Aop源码分析-程序员宅基地

技术标签: spring  java  Powered by 金山文档  前端  springboot  

      <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.12.RELEASE</version>
        </dependency>

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

Aop:指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编码方式;

Aop:的底层就是动态代理

public class Sp{

    public int add(int a,int b){
        int res=1/0;
        return a+b;
    }
}
//让spring知道这是一个切面类
@Aspect
public class Linux {


    //切入点 *表示任何方法 ..表示所有参数
    @Pointcut("execution(public int com.dmg.Sp.*(..))")
    public void pointcut(){

    }

    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        //方法名称
        String methodName=joinPoint.getSignature().getName();
        //参数
        Object[]args=joinPoint.getArgs();
        System.out.println(methodName+"方法,前置通知:,参数:"+ Arrays.asList(args));
    }


    @After("pointcut()")
    public void after(JoinPoint joinPoint){
        //方法名称
        String methodName=joinPoint.getSignature().getName();
        //参数
        Object[]args=joinPoint.getArgs();
        System.out.println(methodName+"方法,后置通知:,参数:"+ Arrays.asList(args));
    }

    @AfterReturning(value = "pointcut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        //方法名称
        String methodName=joinPoint.getSignature().getName();
        //参数
        Object[]args=joinPoint.getArgs();
        System.out.println(methodName+"方法,返回通知:,参数:"+ Arrays.asList(args)+",结果:"+result);
    }

    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e){
        //方法名称
        String methodName=joinPoint.getSignature().getName();
        //参数
        Object[]args=joinPoint.getArgs();
        System.out.println(methodName+"方法,异常通知:,参数:"+ Arrays.asList(args)+",异常信息:"+e);
    }
}
//开启aop代理
@EnableAspectJAutoProxy
@Configuration
public class TestConfig {



    @Bean
    public Linux linux(){
        return new Linux();
    }

    @Bean
    public Sp sp(){
        return new Sp();
    }
}


public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestConfig.class);
        Sp sp = applicationContext.getBean(Sp.class);
        int res = sp.add(11, 22);
        System.out.println(res);
    }
}

执行之后,打印了前置通知,后置通知,和异常通知,也拿到了方法的名字,参数和异常信息

我们吧int res=1/0;注释掉

可以看到这次拿到了结果

我们进入@EnableAspectJAutoProxy源码

在这里会导入一个AspectJAutoProxyRegistrar类到容器中,我们点进去

在这里会注册bean定义的信息

进入registerAspectJAnnotationAutoProxyCreatorIfNecessary方法

注册切面注解自动代理必要时创建

然后进入AnnotationAwareAspectJAutoProxyCreator类

创建注解增强切面自动代理

在这个initBeanFactory方法 初始化bean工厂

我们进入AnnotationConfigApplicationContext 注解配置应用程序上下文的源码

传入配置类,创建ioc容器

注册配置类

调用刷新方法,刷新环境变量,刷新容器

进入refresh()方法,进入this.registerBeanPostProcessors(beanFactory);

注册bean后置处理器

找到PostProcessorRegistrationDelegate类的registerBeanPostProcessors方法这里

先获取ioc容器已经定义了的需要创建对象的索引BeanPostProcessor

给容器中加别的BeanPostProcessor

优先注册实现了PriorityOrdered接口的xxxBeanPostProcessor

在给容器中注册实现了Ordered接口的xxxBeanPostProcessor

注册没实现优先级接口的xxxBeanPostProcessor

然后进入AbstractAutowireCapableBeanFactory抽象自动注入有能力的bean工厂类的

doCreateBean方法

创建bean对象

给bean的各种属性赋值

初始化bean

进入initializeBean方法

处理Aware接口的方法回调

应用后置处理器,初始化之前的操作

执行自定义的初始化方法,比如说@Bean(这里设置初始化方法,销毁方法)

执行后置处理器的,初始化之后的方法

进入invokeAwareMethods方法

判断是否实现的bean工厂的增强接口,然后进入对方的类设置bean工厂

然后进入AbstractAdvisorAutoProxyCreator 抽象顾问自动代理创建器类

的setBeanFactory方法

然后这个时候走到初始化bean工厂的时候,就进入了

AnnotationAwareAspectJAutoProxyCreator注解增强切面自动代理创建器类的initBeanFactory方法

这个类就是我们之前使用开启aop代理哪个类,对应注册到bean定义的一个类

BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功

然后在回到PostProcessorRegistrationDelegate后置处理程序注册委托类的

registerBeanPostProcessors方法

把后置处理程序注册到bean工厂中

上面都是创建和注册AnnotationAwareAspectJAutoProxyCreator的过程

接下来我们进入AbstractAutoProxyCreator 抽象自动代理创建器类的

postProcessBeforeInstantiation方法 后置处理器在实例化之前处理

他继承了InstantiationAwareBeanPostProcessor类

AbstractApplicationContext类中的这一行

this.finishBeanFactoryInitialization(beanFactory);

完成bean工厂初始化工作,创建剩下的单实例bean

点进去 在这一行

beanFactory.preInstantiateSingletons();

创建单例bean

遍历获取容器中所有的Bean,依次创建对象getBean(beanName)

在getBean方法里面又调用了doGetBean

Object sharedInstance = this.getSingleton(beanName);这一行

先从缓存中获取bean是否存在,如果存在,说明已经创建过了,直接使用,

只要创建好的bean,都会被缓存起来

如果没有创建,才去创建

this.createBean这里创建bean

在resolveBeforeInstantiation方法 解析实例化之前操作

希望后置处理器在此能返回一个代理对象,如果能返回代理对象就使用,如果不能就继续

调用doCreateBean 去创建bean 这里才是真正的实例化bean,就和我们上面的结合上了

createBeanInstance 创建bean实例

populateBean bean的属性赋值

initializeBean 初始化bean

invokeAwareMethods 执行增强方法

applyBeanPostProcessorsBeforeInitialization 执行后置处理器初始化之前的操作

applyBeanPostProcessorsAfterInitialization 执行后置处理器初始化之后的操作

进入resolveBeforeInstantiation方法

在这里先执行bean后置处理器实例化之前拿到bean

如果bean不为空的时候,在执行bean后置处理初始化之后的方法

进入applyBeanPostProcessorsBeforeInstantiation方法

可以看到这里判断是否是InstantiationAwareBeanPostProcessor 这个类实例化增强bean后置处理器,然后执行对应的postProcessBeforeInstantiation方法

然后又回到了这里

在这里关心linux的创建,判断当前bean是否在advisedBeans中(保存了所有需要增强bean)

isInfrastructureClass这里判断是否是基础类型的bean,如果是放入advisedBeans中

在这里会判断是否带着@Aspect这个注解

首先会进入wrapIfNecessary方法

然后再进入getAdvicesAndAdvisorsForBean方法

找到4个增强的通知方法

然后进入findAdvisorsThatCanApply方法

然后进入canApply方法 来看增强器是否匹配

sortAdvisors这里给增强器排序

把找到的增强bean返回到一个数组,然后再放入增强beans中

如果不为空,那么创建代理对象

获取所有增强器(通知方法),放入到代理工厂中

用代理工厂帮我们创建对象

创建Aop的代理对象

spring会自动判断选择cglib动态代理,还是jdk动态代理

最后我们可以看到拿到的是cglib动态代理

给容器中返回当前组件使用cglib增强了的代理对象

以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程

接下来我们在看下目标方法执行,也就是sp.add这个方法之前,先拦截

容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象)

然后进入cglib的拦截方法,在这一行

List<Object>chain=this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

根据代理工厂对象,获取将要执行的目标方法拦截器链

如果没有拦截器链,那么直接执行目标方法

如果有拦截器链,那么执行cglib代理的方法,然后去执行我们设置好的前置,后置,返回,异常方法,并吧结果返回给retVal

我们进入getInterceptorsAndDynamicInterceptionAdvice获取拦截器链的方法

如果缓存为空,那么

进入增强链工厂的getInterceptorsAndDynamicInterceptionAdvice方法

遍历所有的增强器,然后转成拦截器,放入拦截器集合

拦截器链(每一个通知方法又被包装为方法拦截器,利用MethodInterceptor机制)

接下来我们在来看下拦截器链的触发过程

进入ReflectiveMethodInvocation反射方法调用类的proceed方法

currentInterceptorIndex 当前拦截器的索引

如果不相等,那么继续进入proceed方法

每次执行proceed方法,当前拦截器的索引就会自增1

先打印前置通知

如果当前拦截器的索引=拦截器链的集合-1,那么通过反射调用目标方法

也就是执行我们的后置,异常,返回通知方法

拦截器链的触发过程

如果没有拦截器执行目标方法,或者拦截器的索引和拦截器数组大小一样,(指定到了最后一个拦截器),执行目标方法

链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行

Aop总结

1.使用@EnableAspectJAutoProxy 开启Aop功能;

2.@EnableAspectJAutoProxy会给容器注册一个组件,AnnotationAwareAspectJAutoProxyCreator是一个后置处理器;

3. registerBeanPostProcessors()注册后置处理器,创建AnnotationAwareAspectJAutoProxyCreator;

4.finishBeanFactoryInitialization() 初始化剩下的单实例bean;

5. 创建业务逻辑组件和切面组件;

6. AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程;

7.组件创建完之后,判断组件是否需要增强;

8.如果是需要增强,切面的通知方法,包装成增强器(Advisor),给业务逻辑组件创建一个代理对象(cglib);

9.代理对象执行目标方法

10.得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)

11.利用拦截器的链式机制,依次进入每一个拦截器进行执行;

12.正常执行:前置通知-》目标方法-》返回通知-》后置通知

13.出现异常:前置通知-》目标方法-》异常通知-》后置通知

大白话Aop总结;

aop就是代理一个方法,然后在目标方法执行之前,做一些拦截的处理;

1.使用@EnableAspectJAutoProxy 开启Aop功能;

2.在切面类加入@Aspect让spring知道这是一个切面类;

3.在完成bean工厂初始化中,会判断是否使用@Aspect这个注解;

4.获取所有的增强器(通知方法),放入到代理工厂中,使用cglib创建Aop动态代理对象;

5.获取拦截器链,然后执行通知方法;

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_21277357/article/details/128881172

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue

推荐文章

热门文章

相关标签