09.AOP-尚硅谷Spring零基础入门到进阶,一套搞定spring6全套视频教程(源码级讲解)

现有代码缺陷
针对带日志功能的实现类,我们发现有如下缺陷:

  • 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
  • 附加功能分散在各个业务功能方法中,不利于统一维护
    解决思路
    解决核心:解耦。把附加功能从业务功能代码中抽取出来。
    困难
    解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引
    入新的技术。

代理模式

概念

介绍
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时
候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中
剥离出来一一解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够
集中在一起也有利于统一维护。
相关术语
,代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
·目标:被代理“套用”了非核心逻辑代码的类、对象、方法。

场景模拟

  1. 声明计算器接口Calculator,包含加减乘除的抽象方法

    package org.example;  public interface Calculator {  public int add(int i, int j);  public int sub(int i, int j);  public int mul(int i, int j);  public int div(int i, int j);  
    }
    
  2. 写一个实现Calculator业务的实现类

    package org.example;
    public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("result=" + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("result=" + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("result=" + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("result=" + result);return result;}
    }
    
  3. 写一个实现Calculator业务的带有日志功能的实现类

    package org.example;  
    public class CalculatorLogImpl implements Calculator {@Overridepublic int add(int i, int j) {System.out.println("计算开始,i=" + i + "j=" + j);int result = i + j;System.out.println("计算结束,i=" + i + "j=" + j + "result=" + result);System.out.println("result=" + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("计算开始,i=" + i + "j=" + j);int result = i - j;System.out.println("计算结束,i=" + i + "j=" + j + "result=" + result);System.out.println("result=" + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("计算开始,i=" + i + "j=" + j);int result = i * j;System.out.println("计算结束,i=" + i + "j=" + j + "result=" + result);System.out.println("result=" + result);return result;}@Overridepublic int div(int i, int j) {System.out.println("计算开始,i=" + i + "j=" + j);int result = i / j;System.out.println("计算结束,i=" + i + "j=" + j + "result=" + result);System.out.println("result=" + result);return result;}
    }
    

静态代理

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其
他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散
的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。
这就需要使用动态代理技术了。

动态代理

使用java.lang.reflect.Proxy类实现动态代理
官方示例代码

InvocationHandler handler = new MyInvocationHandler(...);  
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),  new class<?>[]{Foo.class},  handler);

创建一个代理工厂类

package org.example;  import lombok.val;  import javax.print.attribute.standard.JobKOctets;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  public class ProxyFactory {  Object target;  public ProxyFactory(Object target) {  this.target = target;  }  public Object getProxy() {  
/*      有三个参数  第一个参数:CLassLoader:加载动态生成代理类的来加载器  第二个参数:CLass[]interfaces:目录对象实现的所有接口cLass类型数组  第三个参数:InvocationHandler:设置代理对象实现目标对象方法的过程*/  ClassLoader cLassLoader = target.getClass().getClassLoader();  Class[] classes = target.getClass().getInterfaces();  InvocationHandler invocationHandler = new InvocationHandler() {  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  //调用方法前日志  System.out.println("[动态代理][调用前日志]" + method.getName() + "参数:" + args);  //调用目标方法  Object result = method.invoke(target, args);  //调用方法后日志  System.out.println("[动态代理][调用后日志]" + method.getName() + "参数:" + args);  return result;  }  };  return Proxy.newProxyInstance(cLassLoader, classes, invocationHandler);  }  
}

编写测试类

@Test  
public void calculatorTest(){  ProxyFactory proxyFactory=new ProxyFactory(new CalculatorImpl());  Calculator proxy=(Calculator) proxyFactory.getProxy();  proxy.add(1,1);  
}

输出结果

[动态代理][调用前日志]add参数:[Ljava.lang.Object;@7d0587f1
result=2
[动态代理][调用后日志]add参数:[Ljava.lang.Object;@7d0587f1

基于注解的AOP

动态代理分类:JDK动态代理和cglib动态代理
JDK动态代理生成接口实现类代理对象
cglib动态代理继承被代理的目标类,生成子类代理对象,不需要目标类实现接口

  • 有接口可以使用JDK动态代理和cblib动态代理
  • 没有接口只能使用cblib动态代理
    Aspect:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入"被代理的目标类编译得到的字节码
    文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了Aspect)中的注解。

使用AOP步骤

  1. 引入aop相关依赖

    <!--spring aop依赖-->  
    <dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-aop</artifactId>  <version>6.0.2</version>  
    </dependency>  
    <!--spring aspects依赖-->  
    <dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-aspects</artifactId>  <version>6.0.2</version>  
    </dependency>
    
  2. 创建目标资源

    1. 接口

      package com.example.annoAOP;  public interface Calculator {  public int add(int i, int j);  public int sub(int i, int j);  public int mul(int i, int j);  public int div(int i, int j);  
      }
      
    2. 实现类

      package com.example.annoAOP;  import org.springframework.stereotype.Component;  @Component  
      public class CalculatorImpl implements Calculator {  @Override  public int add(int i, int j) {  int result = i + j;  System.out.println("result=" + result);  return result;  }  @Override  public int sub(int i, int j) {  int result = i - j;  System.out.println("result=" + result);  return result;  }  @Override  public int mul(int i, int j) {  int result = i * j;  System.out.println("result=" + result);  return result;  }  @Override  public int div(int i, int j) {  int result = i / j;  System.out.println("result=" + result);  return result;  }  
      }
      

第三步创建切面类

  1. 创建bean.xml,使用AOP约束,开启AOP功能和扫描功能

    <?xml version="1.0" encoding="UTF-8"?>  
    <beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:context="http://www.springframework.org/schema/context"  xmlns:aop="http://www.springframework.org/schema/aop"  xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd    http://www.springframework.org/schema/context    http://www.springframework.org/schema/context/spring-context.xsd    http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop.xsd">  <!-- 开启组件扫描           -->  <context:component-scan base-package="com.example"/>  <!--开启aspectj自动代理,为目标对象生成代理-->  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  
    </beans>
    
  2. 创建LogAscept类,增加一个方法的前置切入点

    package com.example.annoAOP;  import org.aspectj.lang.annotation.Aspect;  
    import org.aspectj.lang.annotation.Before;  
    import org.springframework.stereotype.Component;  @Aspect//表明这是一个AOP文件  
    @Component//让IoC进行管理  
    public class LogAspect {  //设置切入点和通知类型  //通知类型:  // 前置   @Before(value="切入点表达式")  // 返回   @AfterReturning    // 异常   @AfterThrowing    // 后置   @After()    // 环绕   @Around()    //切入点表达式写法:execution(权限修饰 方法返回值 方法所在全类名.方法名 (参数列表))  //execution:固定语法  //权限修饰:这里写*表示权限修饰符和返回值任意  //方法所在全类名:写*表示任意包名;写*...表示包名任意同时包层次深度任意  //类名用*号代替表示类名任意,部分用*代替,如*Service,表示匹配以Service结尾的列或接口  //方法名:用*号代替表示方法名任意;部分用*代替,如get*,表示匹配以get开头的方法  //参数列表可以使用(...)形式表示参数列表任意  @Before(value = "execution(public int com.example.annoAOP.CalculatorImpl.add (int,int))")  public void beforeAdd() {  System.out.println("[前置通知][add()]计算开始");  }  
    }
    

    方法表达式写法:

    在这里插入图片描述

  3. 创建测试方法

    @Test
    public void testAOPAdd(){ApplicationContext applicationContext=new ClassPathXmlApplicationContext("bean.xml");Calculator calculator=applicationContext.getBean(Calculator.class);calculator.add(1,1);
    }
    
  4. 输出结果

    [前置通知][add()]计算开始
    result=2
    

通知类型

  • 前置通知:在被代理的目标方法前执行
  • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
  • 异常通知:在被代理的目标方法异常结束后执行(死于非命)
  • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
  • 环绕通知:使用try.catch.finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
    修改LogAspect类,添加五种通知方法
package com.example.annoAOP;  import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.*;  
import org.springframework.stereotype.Component;  @Aspect//表明这是一个AOP文件  
@Component//让IoC进行管理  
public class LogAspect {  //前置通知  @Before(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))")  public void beforeMethod(JoinPoint joinPoint) {  String MethodName = joinPoint.getSignature().getName();  Object[] args = joinPoint.getArgs();  System.out.println("[前置通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("Args[]=" + args);  }  //后置通知  @After(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))")  public void afterMethod(JoinPoint joinPoint) {  String MethodName = joinPoint.getSignature().getName();  Object[] args = joinPoint.getArgs();  System.out.println("[后置通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("Args[]=" + args);  }  //返回通知  @AfterReturning(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))", returning = "result")  public void afterReturnMethod(JoinPoint joinPoint, Object result) {  String MethodName = joinPoint.getSignature().getName();  System.out.println("[返回通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("[返回通知]result=" + result);  }  //异常通知  @AfterThrowing(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))", throwing = "exp")  public void afterThrowing(JoinPoint joinPoint, Throwable exp) {  String MethodName = joinPoint.getSignature().getName();  System.out.println("[异常通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println(exp);  }  //环绕通知  @Around("execution(* com.example.annoAOP.CalculatorImpl.* (..))")  //ProceedingJoinPoint继承JoinPoint,比JoinPoint功能更强大,可以更好的调用目标方法  public Object around(ProceedingJoinPoint joinPoint) {  Object result = null;  try {  System.out.println("环绕通知-目标方法执行前");  result = joinPoint.proceed();  System.out.println("环绕通知-目标方法执行后");  } catch (Throwable throwable) {  System.out.println("环绕通知-目标方法执行异常");  } finally {  System.out.println("环绕通知-目标方法执行完成");  }  return result;  }  
}

输出结果

环绕通知-目标方法执行前
[前置通知][CalculatorImpl.MethodName=add()
Args[]=[Ljava.lang.Object;@62727399
result=2
[返回通知][CalculatorImpl.MethodName=add()
[返回通知]result=2
[后置通知][CalculatorImpl.MethodName=add()
Args[]=[Ljava.lang.Object;@62727399
环绕通知-目标方法执行后
环绕通知-目标方法执行完成

编写测试方法,使测试方法引发异常

@Test  
public void testAOPexp(){  ApplicationContext applicationContext=new ClassPathXmlApplicationContext("bean.xml");  Calculator calculator=applicationContext.getBean(Calculator.class);  calculator.div(1,0);  
}

运行结果

环绕通知-目标方法执行前
[前置通知][CalculatorImpl.MethodName=div()
Args[]=[Ljava.lang.Object;@4d9ac0b4
[异常通知][CalculatorImpl.MethodName=div()
java.lang.ArithmeticException: / by zero
[后置通知][CalculatorImpl.MethodName=div()
Args[]=[Ljava.lang.Object;@4d9ac0b4
环绕通知-目标方法执行异常
环绕通知-目标方法执行完成[之后是异常报错信息]

重用切入点

  1. 定义一个切入点

    	package com.example.annoAOP;
    @Pointcut(value = "execution(* com.example.annoAOP.CalculatorImpl.* (..))")  
    public void pointCut() {}
    
  2. 使用切入点

    1. 内部使用切入点

      @After(value = "pointCut")  
      public void afterMethod(JoinPoint joinPoint) {  String MethodName = joinPoint.getSignature().getName();  Object[] args = joinPoint.getArgs();  System.out.println("[后置通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("Args[]=" + args);  
      }  
      
    2. 外部使用切入点

      @After(value = "com.example.annoAOP.pointCut")  
      public void afterMethod(JoinPoint joinPoint) {  String MethodName = joinPoint.getSignature().getName();  Object[] args = joinPoint.getArgs();  System.out.println("[后置通知][CalculatorImpl.MethodName=" + MethodName + "()");  System.out.println("Args[]=" + args);  
      }  
      

切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

  • 优先级高的切面:外面
  • 优先级低的切面:里面
    使用@Order注解可以控制切面的优先级:
  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

XML形式配置AOP

  1. 创建新包xmlaop,复制上文接口、实现类、AOP配置类

  2. 删除LogAspect类的@Aspect注解和AOP注解

  3. 新建XmlAop.xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>  
    <beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:context="http://www.springframework.org/schema/context"  xmlns:aop="http://www.springframework.org/schema/aop"  xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd    http://www.springframework.org/schema/context    http://www.springframework.org/schema/context/spring-context.xsd    http://www.springframework.org/schema/aop    http://www.springframework.org/schema/aop/spring-aop.xsd">  <!-- 开启组件扫描           -->  <context:component-scan base-package="com.example.xmlAOP"/>  <!--配置AOP-->  <aop:config>  <!-- 配置切面类       -->  <aop:aspect ref="logAspect">  <!-- 配置切入点       -->  <aop:pointcut id="cutpoint" expression="execution(* com.example.xmlAOP.CalculatorImpl.* (..))"/>  <!-- 配置方法执行前通知       -->  <aop:before method="beforeMethod" pointcut-ref="cutpoint"/>  <!-- 配置方法执行后通知       -->  <aop:after method="afterMethod" pointcut-ref="cutpoint"/>  <!-- 配置方法返回后通知       -->  <aop:after-returning method="afterReturnMethod" pointcut-ref="cutpoint" returning="result"/>  <!-- 配置环绕通知       -->  <aop:around method="around" pointcut-ref="cutpoint"/>  <!-- 配置异常通知       -->  <aop:after-throwing method="afterThrowing" pointcut-ref="cutpoint" throwing="exp"/>  </aop:aspect>  </aop:config>  </beans>
    
  4. 编写测试方法

    @Test  
    public void testXML_AOP(){ApplicationContext applicationContext=new ClassPathXmlApplicationContext("XmlAop.xml");//本项目存在两个Calculator,需要注意使用的是哪个Calculator类com.example.xmlAOP.Calculator calculator=applicationContext.getBean(com.example.xmlAOP.Calculator.class);calculator.add(1,1);
    }
    
  5. 输出结果

[前置通知][CalculatorImpl.MethodName=add()
Args[]=[Ljava.lang.Object;@eda25e5
环绕通知-目标方法执行前
result=2
环绕通知-目标方法执行后
环绕通知-目标方法执行完成
[返回通知][CalculatorImpl.MethodName=add()
[返回通知]result=2
[后置通知][CalculatorImpl.MethodName=add()
Args[]=[Ljava.lang.Object;@eda25e5

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/45007.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始读RocketMq源码(三)Broker存储Message流程解析

目录 前言 准备 消息载体CommitLog 文件持久化位置 源码解析 broker消息对象MessageExtBrokerInner 异步存储message CommitLog的真相 创建MappedFile文件 加入异步刷盘队列 Message异步存储MappedByteBuffer 总结 前言 在面试中我们经常会听到这样的回答&#x…

国产化趋势下源代码数据防泄密的信创沙盒的方案分享

随着国产化的大力推进&#xff0c;越来越多的企事业单位在逐步替换Windows、Linux等操作系统的使用。那么什是国产化了&#xff1f;国产化是指在产品或服务中采用国内自主研发的技术和标注&#xff0c;替代过去依赖的他国的产品和服务&#xff0c;国产化又被称之为“信创”&…

GitLab CI/CD实现项目自动化部署

1 GitLab CI/CD介绍 GitLab CI/CD 是 GitLab 中集成的一套用于软件开发的持续集成&#xff08;Continuous Integration&#xff09;、持续交付&#xff08;Continuous Delivery&#xff09;和持续部署&#xff08;Continuous Deployment&#xff09;工具。这套系统允许开发团队…

vue里实现点击按钮回到页面顶部功能,博客必备!

效果 步骤 1-标签结构 动态绑定样式style&#xff0c;监听点击事件&#xff0c;后续控制opacity透明度。和滚动距离 <div class"toTop" :style"dynamicStyles" click"toTop"><!--<i class"fa fa-arrow-up"></i>…

超简单的通配证书签发工具,免费,无需安装任何插件到本地

常见的acme.sh 或者 lego等工具需要配置&#xff0c;安装不灵活&#xff0c;续签需要配置计划任务&#xff0c;签发单域名证书或者通配证书需要不同的指令和配置&#xff0c;繁琐&#xff0c;如果自己程序想要对接签发证书的api有的不支持&#xff0c;有的用起来繁琐。 最近发…

【VIVADO SDK调试遇到DataAbortHandler】

问题 SDK调试遇到DataAbortHandler问题。 运行后不显示结果&#xff0c;debug模式下发现进入DataAbortHandler异常函数。程序中存在大数组。 原因:SDK默认的堆栈为1024bytes,需要将堆栈调大。 修改方法&#xff1a; 解决:对application中src下的lscript.ld双击&#xff0c;…

Linux 程序卡死的特殊处理

一、前言 Linux环境。 我们在日常编写的程序中&#xff0c;可能会出现一些细节问题&#xff0c;导致程序卡死&#xff0c;即程序没法正常运行&#xff0c;界面卡住&#xff0c;也不会闪退... 当这种问题出现在客户现场&#xff0c;那就是大问题了。。。 当我们暂时还无法排…

如何定量选择孔销基准?-DTAS来帮你!

在当今快速发展的工程领域&#xff0c;公差仿真的作用日渐重要&#xff0c;在公差仿真中&#xff0c;基准体系的选择对于最终结果更是至关重要。基准体系不同可能导致仿真过程中的参数计算、误差分析以及最终的工程设计都有所不同。基准体系作为评估和比较的参照&#xff0c;直…

Suricata引擎二次开发之命中规则定位

二开背景 suricata是一款高性能的开源网络入侵检测防御引擎&#xff0c;旨在检测、预防和应对网络中的恶意活动和攻击。suricata引擎使用多线程技术&#xff0c;能够快速、准确地分析网络流量并识别潜在的安全威胁&#xff0c;是众多IDS和IPS厂商的底层规则检测模块。 前段时间…

强制升级最新系统,微软全面淘汰Win10和部分11用户

说出来可能不信&#xff0c;距离 Windows 11 正式发布已过去整整三年时间&#xff0c;按理说现在怎么也得人均 Win 11 水平了吧&#xff1f; 然而事实却是&#xff0c;三年时间过去 Win 11 占有率仅仅突破到 29%&#xff0c;也就跳起来摸 Win 10 屁股的程度。 2024 年 6 月 Wi…

【Linux】磁盘性能压测-FIO工具

一、FIO工具介绍 fio&#xff08;Flexible I/O Tester&#xff09;是一个用于评估计算机系统中 I/O 性能的强大工具。 官网&#xff1a;fio - fio - Flexible IO Tester 注意事项&#xff01; 1、不要指定文件系统名称&#xff08;如/dev/mapper/centos-root)&#xff0c;避…

react启用mobx @decorators装饰器语法

react如果没有经过配置&#xff0c;直接使用decorators装饰器语法会报错&#xff1a; Support for the experimental syntax ‘decorators’ isn’t currently enabled 因为react默认是不支持装饰器语法&#xff0c;需要做一些配置来启用装饰器语法。 step1: 在 tsconfig.js…

【学术会议征稿】第三届能源互联网及电力系统国际学术会议(ICEIPS 2024)

第三届能源互联网及电力系统国际学术会议&#xff08;ICEIPS 2024&#xff09; 2024 3rd International Conference on Energy Internet and Power Systems 能源互联网是实现新一代电力系统智能互动、开放共享的重要支撑技术之一&#xff0c;也是提升能源调度效率&#xff0…

Jetson-AGX-Orin 非docker环境源码编译安装CyberRT

Jetson-AGX-Orin 非docker环境源码编译安装CyberRT 1、安装依赖 sudo apt update sudo apt-get install g gdb gcc cmake sudo apt install libpoco-dev uuid-dev libncurses5-dev python3-dev python3-pip python3 -m pip install protobuf3.14.02、下载CyberRT源码 git cl…

python+pygame实现五子棋人机对战之三

上回讲过&#xff1a; pythonpygame实现五子棋人机对战之一 pythonpygame实现五子棋人机对战之二 界面已经有了&#xff0c;并且可以支持鼠标操作选择菜单和人机对战开始下棋了&#xff0c;那电脑是如何应手落子呢&#xff1f;以下内容是通用的类&#xff0c;全部放在utils.…

全球高端销量第一 凯迪仕智能锁建博会获重磅大奖再次遥遥领先

2024年7月11日&#xff0c;第26届中国广州建博会圆满落幕。Kaadas凯迪仕第11年受邀参展&#xff0c;凭借超吸睛的赛博风展馆和重磅旗舰传奇大师K70系列智能锁震撼亮相&#xff0c;吸引抖音网红云集打卡直播以及众多主流及行业媒体聚集报道。在大家居建装行业全球第一展的舞台上…

问题清除指南|Dell OptiPlex 7070 升级 win11 开启 TPM 2.0 教程

前言&#xff1a;最近想把实验室台式机的系统从 Windows 10 升级到 Windows 11&#xff0c;遇到一点小问题&#xff0c;在此记录一下解决办法。 ⚠️ 注&#xff1a;本教程仅在 Dell OptiPlex 7070 台式机系统中测试有效&#xff0c;并不保证其余型号机器适用此教程。 参考链接…

中国科学院地理所牛书丽团队《Global Change Biology 》最新成果!

本文首发于“生态学者”微信公众号&#xff01; 在全球气候变化的背景下&#xff0c;干旱地区的扩张对生态系统的氮循环产生了深远影响。氮同位素&#xff08;δ15N&#xff09;的天然丰度&#xff0c;尤其是土壤中的δ15N&#xff0c;是评估陆地生态系统氮循环动态和氮限制的关…

【ARMv8/v9 GIC 系列 1.7 -- GIC PPI | SPI | SGI | LPI 中断使能配置概述】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 GIC 各种中断使能配置PPIs(每个处理器私有中断)SPIs(共享外设中断)SGIs(软件生成的中断)LPIs(局部中断)GIC 各种中断使能配置 在ARM GICv3和GICv4架构中,不同类型的中断(如PPIs、SPIs、SGIs和LPIs)可以通过不同的方式进…

分享:2024好的ai文章生成器下载资源 tzqsbic

在当今数字化的时代&#xff0c;ai技术的发展日新月异&#xff0c;为我们的生活和工作带来了诸多便利。其中&#xff0c;ai文章生成器作为一项创新的工具&#xff0c;给当代人们带来了很多好处&#xff0c;尤其是对于很多创作者&#xff0c;不仅能解决创作困难&#xff0c;而且…