Spring注解驱动开发--AOP底层原理

Spring注解驱动开发–AOP底层原理

21. AOP-AOP功能测试

AOP:【动态代理】
指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;
1、导入aop模块:Spring AOP,(Spring-aspects)
2、定义一个业务逻辑类(MathCalculator):在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常、xxx)
3、定义一个日志切面类(LogAspects);切面类里面的方法需要动态感知MathCalculator,div运行到哪里然后哪里就执行。
通知方法:
前置通知(@Before):logStart:在目标方法(div)运行之前运行
后置通知(@After):logENd:在目标方法(div)运行结束之后运行
返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
环绕通知(@Around):动态代理:手动推进目标方法运行(joinPoint.procces())
4、给切面类的目标方法标注何时何地地运行(通知注释)
5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中
6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
【7】、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】
在Spring中很多的 @EnableXXX;
三步:
1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
3)、开启基于注解的aop模式:@EnableAspectJAutoProxy

第一步、导入aop模块

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>

第二步、定义一个业务逻辑类(MathCalculator)

public class MathCalculator {

    public int div(int i,int j){
        System.out.println("MathCalculator...div...");
        return i/j;
    }

}

第三步、定义一个日志切面类(LogAspects)

/**
 * 切面类
 *
 * @Aspect:告诉Spring当前类是一个切面类
 */
@Aspect  //告诉Spring当前类是一个切面类
public class LogAspects {

    //抽取公共的切入点表达式
    //1.本类引用
    //2.其他的切面引用
    @Pointcut("execution(public int com.xjz.aop.MathCalculator.*(..))")
    public void pointCut(){};

    //@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println(""+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+ Arrays.asList(args) +"}");
    }

    @After("com.xjz.aop.LogAspects.pointCut()")
    public void logEnd(JoinPoint joinPoint){
        System.out.println(""+joinPoint.getSignature().getName()+"结束。。。@After");
    }

    //JoinPoint一定要出现在参数表的第一位
    @AfterReturning(value = "pointCut()",returning = "result")
    public void logReturn(JoinPoint joinPoint,Object result){
        System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:运行结果:{"+result+"}");
    }

    @AfterThrowing(value = "pointCut()",throwing = "exception")
    public void logException(JoinPoint joinPoint,Exception exception){
        System.out.println(""+joinPoint.getSignature().getName()+"异常。。。@AfterThrowing:异常信息:{"+exception+"}");
    }
}

第四步、将切面类和业务逻辑类(目标方法所在类)都加入到容器中

@EnableAspectJAutoProxy  //开启基于注解的aop模式
@Configuration
public class MyConfigOfAOP {

    //业务逻辑类加入到容器中
    @Bean
    public MathCalculator calculator(){
        return new MathCalculator();
    }

    //切面类加入到容器中
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }

}

第五步、测试

public class IOCTest_AOP {

    @Test
    public void test01(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigOfAOP.class);

        //1.不要自己创建对象
//        MathCalculator mathCalculator = new MathCalculator();
//        mathCalculator.div(1,1);
        MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
        mathCalculator.div(1,1);

        applicationContext.close();
    }
}

第六步、运行结果

  • 正常返回

image-20230315222208570

  • 异常报错

image-20230315222253906

22. AOP原理

AOP:【动态代理】
     指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;
1、导入aop模块:Spring AOP,(Spring-aspects)
2、定义一个业务逻辑类(MathCalculator):在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常、xxx)
3、定义一个日志切面类(LogAspects);切面类里面的方法需要动态感知MathCalculator,div运行到哪里然后哪里就执行。
     通知方法:
            前置通知(@Before):logStart:在目标方法(div)运行之前运行
            后置通知(@After):logENd:在目标方法(div)运行结束之后运行
            返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
            异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
            环绕通知(@Around):动态代理:手动推进目标方法运行(joinPoint.procces())
4、给切面类的目标方法标注何时何地地运行(通知注释)
5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中
6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)7】、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】
        在Spring中很多的 @EnableXXX;
三步:
     1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
     2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
     3)、开启基于注解的aop模式:@EnableAspectJAutoProxy
AOP原理:【看给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么?】
     @EnableAspectJAutoProxj1@EnableAspectJAutoProxj是什么?
        @Import(AspectJAutoProxyRegistrar.class):给容器中导入AspectJAutoProxyRegistrar
        利用AspectJAutoProxyRegistrar自定义给容器中注册bean;BeanDefinetion
        internalAutoProxyCreator = AnnotationAwareAspectJAutoProxyCreator;
        给容器中注册一个AnnotationAwareAspectJAutoProxyCreator2AnnotationAwareAspectJAutoProxyCreator
      AnnotationAwareAspectJAutoProxyCreator
          ->AspectJAwareAdvisorAutoProxyCreator
             ->AbstractAdvisorAutoProxyCreator
                 ->AbstractAutoProxyCreator
                     implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware
                  关注后置处理器(在bean初始化完成前后做事情)、自动装配BeanFactoryAware
 AbstractAutoProxyCreator.setBeanFactory()
 AbstractAutoProxyCreator.有后置处理器的逻辑;
 AbstractAdvisorAutoProxyCreator.setBeanFactory()-->initBeanFactory()
 AnnotationAwareAspectJAutoProxyCreator.initBeanFactory()
 流程:
        1)、传入配置类,创建ioc容器
        2)、注册配置类,调用refresh()刷新容器;
        3)registerBeanPostProcessors(beanFactory);注册bean的后置处理器来方便拦截bean的创建;
             1)、先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor
             2)、给容器中加别的BeanPostProcessor
             3)、优先注册实现了呢PriorityOrdered接口的BeanPostProcessor4)、再给容器中注册实现了Ordered接口和BeanPostProcessor5)、注册没实现优先级接口的BeanPostProcessor
             6)、注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中;
                  创建internalAutoProxyCreator的BeanPostProcessorAnnotationAwareAspectJAutoProxy1)、创建Bean的实例;
                  2)、populateBean:给bean的各种属性赋值
                  3)、initializeBean:初始化bean;
                         1)invokeAwareMethods():处理Aware接口的方法回调
                         2)applyBeanPostProcessorBeforeInitialization():应用后置处理器的postProcessBefore
                         3)invokeInitMethods():执行自定义的初始化方法
                         4)applyBeanPostProcessorAfterInitialization():执行后置处理器的postProcessAfterIn
                  4)BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功:--》aspectJAdvis
            7)、把BeanPostProcessor注册到BeanFactory中:
                 beanFactory.addBeanPostProcessor(postProcessor);
========以上是创建和注册AnnotationAwareAspectJAutoProxyCreator的过程===========
      AnnotationAwareAspectJAutoProxyCreator =InstantiationAwareBeanPostProcessor
      4)finishBeanFactoryInitialization(beanFactory);完成BeanFactory初始化工作;创建剩下的单实例Bean
          1)、遍历获取容器中所有的Bean,依次创建对象getBean(beanName);
               getBean->doGetBean()->getSingleton()->
          2)、创建bean
              【AnnotationAwareAspectJAutoProxyCreator在所有bean创建之前会有一个拦截,InstantiationAwareBeanPostP
              1)、先从缓存中获取当前bean,如果能获取到,说明bean是之前被创建过的,直接使用,否则再创建;
                  只要创建好的Bean都会被缓存起来
              2)createBean();创建bean;AnnotationAwareAspectJAutoProxyCreator会在任何bean创建之前先尝试返回bean的实例
                 【BeanPostProcessor是在Bean对象创建完成初始化前后调用的】
                 【InstantiationAwareBeanPostProcessor是在创建Bean实例之前先尝试用后置处理器返回对象的】
                 1)resolveBeforeInstantiation(beanName,mbdToUse);解析BeforeInstantiation
                     希望后置处理器在此前能返回一个代理对象;如果能返回代理对象就使用,如果不能就继续
                     1)、后置处理器先尝试返回对象:
                         bean = applyBeanPostProcessorsBeforeInstantiation();
                             拿到所有后置处理器,如果是InstantiationAwareBeanPostProcessor;
                             就执行postProcessBeforeInstantiation
                         if (bean != null) {
                             bean = applyBeanPostProcessorsBeforeInstantiation(bean,beanName)
                         }
                 2)doCreateBean(beanName, mbdToUse, args);真正的去创建一个bean实例;和3.6流程一样
                 3)AnnotationAwareAspectJAutoProxyCreatorInstantiationAwareBeanPostProcessor】的使用:
              1)、每一个bean创建之前,调用postProcessBeforeInstantiation();
                 关心MathCalculatorLogAspect的创建
                 1)、判断当前bean是否在advisedBeans中(保存了所有需要增强bean)
                 2)、判断当前bean是否是基础类型的AdvicePointcutAdvisorAOPInfrastructureBean.
                     或者是否是切面(@Aspect)
                 3)、是否需要跳过
                     1)、获取候选的增强器(切面里面的通知方法)List<Advisor、candidateAdvisors】
                         每一个封装的通知方法的增强器是InstantiationModelAwarePointcutAdvisor;
                         判断每一个增强器是否是AspectJPointcutAdvisor类型的;返回true
                     2)、永远返回false
               2)、创建对象
              postProcessAfterInitialization;
                 return wrapIfNecessary(bean, beanName,cacheKey);//包装如果需要的情况下
                 1)、获取当前bean的所有增强器(通知方法)
                     1、找到候选的所有的增强器(找哪些通知方法是需要切入当前bean方法的)
                     2、获取到能在bean使用的增强器。
                     3、给增强器排序
                 2)、保存当前bean在advisedBeans中:
                 3)、如果当前bean需要增强,创建当前bean的代理对象;
                     1)、获取所有增强器(通知方法)
                     2)、保存到proxyFactory
                     3)、创建代理对象;Spring自动决定
                         JdkDynamicAopProxy(config);jdk动态代理
                         ObjenesisCglibAopProxy(config);cglib的动态代理
	                  4)、给容器中返回当前组件使用cglib增强了的代理对象;
	                  5)、以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程
	            3)、目标方法执行:
	              容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx);
	                  1)CglibAopProxy.intercept();拦截目标方法的执行
	                  2)、根据ProxyFactory对象获取将要执行的目标方法拦截器链;
	                    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvi
	                    1)List<Object> interceptorList保存所有拦截器
	                        一个默认的ExposeInvocationinterceptor4个增强器;
	                    2)、遍历所有的增强器,将其转为Interceptor
	                        registry.getInterceptors(advisor);
	                    3)、 将增强器转为List<MethodInterceptor>数组;
	                          如果是MethodInterceptor,直接加入到集合中
	                          如果不是,使用AdvisorAdapter将增强器转为MethodINterceptor;
	                          转换完成返回MethodInterceptor数据;
            3)、如果没有拦截器链,直接执行目标方法;
                拦截器链(每一个通知方法又被包装为方法拦截器)
            4)、如果有拦截器链,把需要执行的目标对象、目标方法,拦截器链等信息传入创建一个CglibMethodInvocation对象;
                并调用 Object retVal = mi.proceed();
            5)、拦截器链的触发过程
                1)、如果没有拦截器执行目标方法,或者拦截器的索引和拦截器数据-1大小一样(指定到了最后一个拦截器)执行目标方法;
                2)、链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行;
                     拦截器链的机制,保证通知方法与目标方法的执行顺序
  总结:
       1)@EnableAspectJAutoProxy 开启AOP功能
       2)@EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator
       3)AnnotationAwareAspectJAutoProxyCreator是一个后置处理器;
       4)、容器的创建流程:
           1)registerBeanPostProcessors() 注册后置处理器;创建 AnnotationAwareAspectJAutoProxyCreator对象
           2)finishBeanFactoryInitialization()初始化剩下的单实例bean
               1)、创建业务逻辑组件和切面组件
               2)AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程
               3)、组件创建完之后,判断组件是否需要增强
                   是:切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib)5)、执行目标方法:
           1)、代理对象执行目标方法
           2)CglibAopProxy.intercept();
               1)、得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)
               2)、利用拦截器的链式机制,依次进入每一个拦截器进行执行;
               3)、效果:
                     (Spring4):正常执行,前置通知-》目标方法-》后置通知-》返回通知
                                出现异常,前置通知-》目标方法-》后置通知-》异常通知
                     (Spring5):正常执行,前置通知-》目标方法-》返回通知-》后置通知
                                出现异常,前置通知-》目标方法-》异常通知-》后置通知

image-20230317225842693

**注意:**一定要跟着雷老师进源码,不然很快就会忘掉的!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Git和Github的基本用法(内含如何下载)

Git和Github的基本用法背景下载安装安装 git for windows使用 Github 创建项目注册账号创建项目下载项目到本地Git工作流程Git 操作的三板斧放入代码三板斧第一招: git add三板斧第二招: git commit三板斧第三招: git push小结背景 git是一个版本控制工具. 主要解决三个问题 代…

[ROC-RK3568-PC] [Firefly-Android] 10min带你了解I2C的使用

&#x1f347; 博主主页&#xff1a; 【Systemcall小酒屋】&#x1f347; 博主追寻&#xff1a;热衷于用简单的案例讲述复杂的技术&#xff0c;“假传万卷书&#xff0c;真传一案例”&#xff0c;这是林群院士说过的一句话&#xff0c;另外“成就是最好的老师”&#xff0c;技术…

蓝桥杯嵌入式第八课--EEPROM读写

前言E2PROM的读写主要是考察IIC的使用&#xff0c;但是在比赛当中I2C的各种驱动文件都是直接给出的&#xff0c;因此我们需要做的工作就是根据EEPROM的读写时序配出读写的函数来。EEPROM硬件连接图我们可以看到IIC的数据线&#xff08;已上拉&#xff09;有两路去处&#xff0c…

C语言详解KMP算法

如果给你一个字符串 和 该字符串的一个子字符串 你能否快速找出该子字符串的所在位置我猜 这里会有一群杠精 说可以找到 真的吗 那下面这个字符串你可以一眼看出来吗你能找出来吗 如果能 算你眼神好 如果不能 那就看看接下来我怎么做你有想到暴力求解法吗&#xff1f;——来自百…

Hadoop运行模块

二、Hadoop运行模式 1&#xff09;Hadoop官方网站&#xff1a;http://hadoop.apache.org 2&#xff09;Hadoop运行模式包括&#xff1a;本地模式、伪分布式模式以及完全分布式模式。 本地模式&#xff1a;单机运行&#xff0c;只是用来演示一下官方案例。生产环境不用。伪分…

【数据结构与算法】栈的实现(附源码)

目录 一.栈的概念和结构 二.接口实现 A.初始化 Stackinit 销毁 Stackdestroy 1.Stackinit 2.Stackdestroy B.插入 Stackpush 删除 Stackpop 1.Stackpush 2.Stackpop C.出栈 Stacktop D. 栈的有效元素 Stacksize 判空 Stackempty 1.Stacksize 2.Stackempty …

【DBC专题】-12-不同类型报文(应用/诊断/网关/测量标定)在DBC中配置,以及在Autosar各模块间的信号数据流向

点击返回「Autosar从入门到精通-实战篇」总目录 案例背景(共18页精讲)&#xff1a;该篇博文将告诉您&#xff1a; 1)Autosar中&#xff0c;不同类型报文(App应用&#xff0c;UDS/OBD诊断&#xff0c;NM网络管理报文&#xff0c;XCP测量标定)的信号数据流向&#xff1b; 2)CAN …

Linux串口应用编程

一、 串口API 在Linux系统中,操作设备的统一接口就是:open/ioctl/read/write。 对于UART,又在ioctl之上封装了很多函数,主要是用来设置行规程。 所以对于UART,编程的套路就是: open设置行规程,比如波特率、数据位、停止位、检验位、RAW模式、一有数据就返回read/write 怎么设置…

C语言刷题(7)(字符串旋转问题)——“C”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰的内容依旧是复习之前的知识点&#xff0c;那么&#xff0c;就是做一道小小的题目啦&#xff0c;下面&#xff0c;让我们进入C语言的世界吧 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; A…

再也不想去字节跳动面试了,6年测开面试遭到这样打击.....

前几天我朋友跟我吐苦水&#xff0c;这波面试又把他打击到了&#xff0c;做了快6年软件测试员。。。为了进大厂&#xff0c;也花了很多时间和精力在面试准备上&#xff0c;也刷了很多题。但题刷多了之后有点怀疑人生&#xff0c;不知道刷的这些题在之后的工作中能不能用到&…

【ChatGPT】论文阅读神器 SciSpace 注册与测试

【ChatGPT】论文阅读神器 SciSpace 注册与测试1. 【SciSpace】网址与用户注册1.1 官网地址&#xff1a;[【SciSpace官网】https://typeset.io](https://typeset.io)1.2 官网注册2. 【SciSpace】实战解说2.1 导入论文2.2 论文分析2.3 中文分析2.4 论文分析进阶2.5 公式表格分析3…

结构体全解,适合初学者的一条龙深度讲解(附手绘图详解)

我们知道&#xff0c;C语言是允许我们自己来创造类型的&#xff0c;这些类型就叫做——自定义类型。 自定义类型又包括结构体类型&#xff0c;联合体类型还有枚举类型。 今天的文章&#xff0c;我们就着重讲解这其中的结构体类型。 目录 结构体的声明 1.1结构的基础知识 1…

Spring和MaBatis整合(xml版与纯注解版)

Spring和MyBatis整合xml版&#xff1a; 先瞅一眼各种文件路径&#xff1a; 将之前mybatis中的测试类中的SqlSessionFactory&#xff08;通过其openSession()来获得对象SqlSession&#xff09;&#xff0c;和Mybatis配置文件中的数据源&#xff08;url&#xff0c;username等&…

【C++】入门知识之 函数重载

前言提到重载这个词&#xff0c;我们会想到什么呢&#xff1f;重载有一种一词多义的意思&#xff0c;中华文化博大精深&#xff0c;之前有一个笑话&#xff0c;中国的乒乓球谁都打不过&#xff0c;男足谁都打不过&#xff0c;哈哈哈这也是非常有意思的&#xff0c;但是今天我们…

【UML】软件需求说明书

目录&#x1f981; 故事的开端一. &#x1f981; 引言1.1编写目的1.2背景1.3定义1.4参考资料二. &#x1f981; 任务概述2.1目标2.2用户的特点2.3假定和约束三. &#x1f981; 需求规定3.1 功能性需求3.1.1系统用例图3.1.2用户登录用例3.1.3学员注册用例3.1.4 学员修改个人信息…

单片机能运行操作系统吗?

先直接上答案&#xff1a;可以&#xff01;但是操作系统不是刚需&#xff0c;上操作系统比较占用单片机的资源&#xff0c;比如占用比较多的FLASH和RAM&#xff0c;间接增加了硬件成本&#xff0c;哪怕成本增加1毛钱&#xff0c;对于上量的产品&#xff0c;分分钟是一个工程师的…

GPT-4,终于来了!

就在昨天凌晨&#xff0c;OpenAI发布了多模态预训练大模型GPT-4。 这不昨天一觉醒来&#xff0c;GPT-4都快刷屏了&#xff0c;不管是在朋友圈还是网络上都看到了很多信息和文章。 GPT是Generative Pre-trained Transformer的缩写&#xff0c;也即生成型预训练变换模型的意思。…

JVM高频面试题

1、项目中什么情况下会内存溢出&#xff0c;怎么解决&#xff1f; &#xff08;1&#xff09;误用固定大小线程池导致内存溢出 Excutors.newFixedThreadPool内最大线程数是21亿(2) 误用带缓冲线程池导致内存溢出最大线程数是21亿(3)一次查询太多的数据&#xff0c;导致内存占用…

深入剖析Linux——进程信号

致前行的人&#xff1a; 要努力&#xff0c;但不着急&#xff0c;繁花锦簇&#xff0c;硕果累累都需要过程&#xff01; 目录 1.信号概念 1.1生活角度的信号 2. 技术应用角度的信号 3.Linux操作系统中查看信号 4.常用信号发送 4.1通过键盘发送信号 4.2调用系统函数发送信号 4.3…

SpringCloud五大核心组件

Consul 等&#xff0c;提供了搭建分布式系统及微服务常用的工具&#xff0c;如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等&#xff0c;满足了构建微服务所需的所有解决方案。 服务发现——Netflix Eureka …
最新文章