Spring源码系列-Spring AOP

目录

AOP的用法

注解用法

早期的基于接口的用法

递归实现的责任链模式

简单实现

责任链模式就两个关键点

传统的aop实现方式的局限性

Advisor

解决不能精确到方法级别的增强

注解形式的Advisor

解决需要创建多个FactoryBean

纯注解的AOP实现原理

AOP源码解析

AOP入口

@EnableAspectJProxy

AOP中的三大BeanPostProcessor

Spring Aop的三种实现方式

解析切面类

AnnotationAwareAspectJAutoProxxyCreator

Spring代码的对老代码的兼容性

cglib动态代理和jdk动态代理

创建动态代理

调用代理方法


AOP 要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。

AOP 的实现并不是因为 Java 提供了什么神奇的钩子,可以把方法的几个生命周期告诉我们,而是我们要实现一个代理,实际运行的实例其实是生成的代理类的实例 

Spring源码系列-Spring AOP_第1张图片

5a87bc01ec9940d9808400514a6ef01e.png

Advisor是源码中的概念,应用本身不需要关心。织入,就是通过动态代理来实现将增强逻辑织入到目标对象的目标方法上去的 

AOP的用法

注解用法

Spring源码系列-Spring AOP_第2张图片

Spring源码系列-Spring AOP_第3张图片

Spring源码系列-Spring AOP_第4张图片

SpringAop只用了AspectJ的注解,还有切点定义切点解析之类的东西

Spring AOP只能用户ioc容器中的bean实例的方法级别进行增强,如果不是ioc容器中的bean,那么spring AOP就无能为力了

早期的基于接口的用法

基于接口的各种类型的通知Advice

早期没有引入AspectJ的切点、切面这一套时,是没有切面的

Spring源码系列-Spring AOP_第5张图片

Spring源码系列-Spring AOP_第6张图片

这里是通过方法拦截器的方式,来给目标方法做一些增强;

方法拦截器的这种形式,可以理解成为环绕通知;

Spring源码系列-Spring AOP_第7张图片

Spring源码系列-Spring AOP_第8张图片

实现AOP,可以使用Advice通知,也可以使用方法拦截器,因为它们有共同的父类Advice;方法拦截器,也是Advice接口的子实现

Spring源码系列-Spring AOP_第9张图片

 这里就是给目标方法,指定了两个通知,tulingLogAdvice和tulingLogInterceptor(tulingLogAdvice和tulingLogInterceptor也就是通过@Bean注入到ioc容器中的两个bean),传入了两个通知,这两个通知就形成了一个通知链

此时,用法非常的死板,只能一次针对一个Bean来创建动态代理对象,如果我有十个bean需要生成动态代理,那么这里就要显示的定义十个不同的ProxyFactoryBean

Spring源码系列-Spring AOP_第10张图片

执行结果:

Spring源码系列-Spring AOP_第11张图片

通过这种方式创建一个动态代理对象,并使用

ProxyFactoryBean#getObject()

就是通过内部生成一个不同通知的责任链,来依次调用不同的通知

Spring源码系列-Spring AOP_第12张图片

可以看到,这里252行在创建动态代理对象之前,首先就是初始化出来一个通知链

递归实现的责任链模式

简单实现

Spring源码系列-Spring AOP_第13张图片

第23行,因为TulingLogAdvice没有实现MethodInterceptor接口,所以我们又自定义了一个委托MethodBeforeAdviceInterceptor

Spring源码系列-Spring AOP_第14张图片

可以看到,这里是适配器和责任链的组合,不同的拦截器形成的责任链。这里,使用的是递归形式的责任链

Spring源码系列-Spring AOP_第15张图片

这里就采用的是,“递归 + 列表索引”的方式,来实现的责任链模式这里的MyMethodInvocation就相当于,标准责任链模式中的FilterChain,FilterChain中也有一个List,这里MyMethodInvocation是有一个List

责任链模式就两个关键点

  • 统一的节点抽象,继承统一的父类或者实现统一的接口,从而方便统一的调用
  • 通过递归,循环,或者next指针的方式来进行调用

Spring源码系列-Spring AOP_第16张图片

可以看到这里56行,是把整个目标对象都传递了进入,而不是传递的目标对象的具体目标方法,这样就会导致写了一套增强逻辑,这套增强逻辑就会对目标对象的每个方法都生效

传统的aop实现方式的局限性

  • 问题一,不能精确到方法级别的aop增强,而是为类中的每个方法都进行了增强
  • 问题二,需要创建很多FactoryBean,针对每个目标对象都要创建一个FactoryBean

Advisor

解决不能精确到方法级别的增强

因为问题一,不能精确到方法级别的aop增强,引入了Advisor的概念

Spring源码系列-Spring AOP_第17张图片

这里一个我们通过接口自定义的Advice,就被封装成了一个的Advisor,后面每个定义的不同的Advice都会被封装成了一个个的Advisor

后面通过注解定义的aop,这里每个@Before、@After,也都会被封装成一个个的Advisor

Spring源码系列-Spring AOP_第18张图片

按照正则表达式匹配,或者按照方法名进行匹配,

执行结果:

Spring源码系列-Spring AOP_第19张图片

这里,就精确到了目标对象的div方法

Spring源码系列-Spring AOP_第20张图片 Spring源码系列-Spring AOP_第21张图片

所以,有了Advisor后就能让aop增强精确到方法级别,Advisor的作用:

  • 包含前置通知、后置通知等增强逻辑
  • 指定要增强的方法名

注意,Advisor并不知道目标对象是谁,目标对象还需要单独指定,代表要把Advisor的增强逻辑附加在哪些目标对象之上

注解形式的Advisor

Spring源码系列-Spring AOP_第22张图片

后面通过注解定义的aop,这里每个@Before、@After都会被封装成一个个的Advisor 

Spring源码系列-Spring AOP_第23张图片

解决需要创建多个FactoryBean

问题二:需要创建很多FactoryBean,从而引入BeanPostProcessor

因为每创建一个目标对象的动态代理,就要重新创建一个ProxyFactoryBean。通过ProxyFactoryBean这种手动的方式来创建动态代理对象

Spring源码系列-Spring AOP_第24张图片

通过BeanNameAutoProxyCreator这个BeanPostProcessor这种方式,动态扫描ioc中的所有bean,只要这个bean的beanName是以“tuling”开头的,那么就给这个bean来创建动态代理对象,动态代理的逻辑就是tulingLogAspectAdvisor中的通知逻辑(注意,这里使用了beanName通配符),指定的tuling*实际上也就是指定目标对象(Advisor本身并不知道目标对象是谁,目标对象还需要单独指定,代表要把Advisor的增强逻辑附加在哪些目标对象之上

纯注解的AOP实现原理

纯注解的方式的aop的实现原理,每个通知,都对应创建一个Advisor,每个Advisor中都有自己专属的有Advice和Pointcut

Spring源码系列-Spring AOP_第25张图片

因为当前,通过BeanPostProcessor来生成动态代理对象时,是以传入的每个Adisor为基本增强单位的

首先,扫描ioc中的所有bean,看哪些bean上被标注了@Aspect注解,如果被标注了,则把里面的@Before、@After等全部变成一个个的Advisor

其次,在ioc的getBean创建bean时,就会通过给BeanNameAutoProxyCreator这个BeanPostProcessor来创建目标对象的动态代理对象,动态代理的给原实例增强的逻辑,就是传进BeanNameAutoProxyCreator中的一个个Adisor。而这些Adisor也就是上面扫描出来的一个个Adisor

Spring源码系列-Spring AOP_第26张图片

ioc加载doCreateBean()时创建当前bean时,会先拿到提前解析好的所有的Advisor,然后循环所有的Advisor,一一和当前bean的配对,如果配对上,那么就需要给当前bean做动态代理增强,增强的逻辑就是匹配上的Advisor中Advice的逻辑

Advisor的PointCut有三种不同的类型

  • 按方法名的
  • 按正则表达式的
  • 按AspectJ表达式的

这里的PointCut的匹配策略有很多种,也就是策略模式,

AOP源码解析

Aop核心三个步骤

  • 解析切面
  • 创建动态代理
  • 调用代理方法

AOP入口

@EnableAspectJProxy

Spring源码系列-Spring AOP_第27张图片

以后,只要是看spring ioc集成什么自身组件,或者第三方组件,一般都会加上一个@EnableXxxxx的注解,我们要找这个组件入口,就从这个注解开始,这也就是spring的一个灵活性

@EnableXxxxx注解中又有一个@Import注解

Spring源码系列-Spring AOP_第28张图片

Spring源码系列-Spring AOP_第29张图片 Spring源码系列-Spring AOP_第30张图片

AnnotationAwareAspectJAutoProxxyCreator这个BeanPostProcessor,在这里被注册到ioc容器的beanDefinitionMap中去,然后ApplicationContext#refresh()中有一步registerBeanPostProcessor()就会实例化这个BeanPostProcessor。

后续这些实例化好的BeanPostProcessor,就会在9大BeanPostProcessor执行的时机被分别的调起

AOP中的三大BeanPostProcessor

Spring源码系列-Spring AOP_第31张图片

看这个实现类,实现的三个接口,就是实现aop的三个关键接口,实现这三个接口的作用分别是:

  • 生成动态代理对象
  • 解析切面
  • 解决循环依赖中aop动态代理对象生成问题

AspectJ 本身是不支持运行期织入的,日常使用时候,我们经常回听说,spring 使用了aspectJ实现了aop,听起来好像spring的aop完全是依赖于aspectJ
其实spring对于aop的实现是通过动态代理(jdk的动态代理或者cglib的动态代理),它只是使用了aspectJ的Annotation,并没有使用它的编译期和织入器

aspectJ是在编译期修改了方法(类本身的字节码被改了),所以可以很轻松地实现调用自己的方法时候的增强。
3)spring aop的代理必须依赖于bean被spring管理,所以如果项目没有使用spring,又想使用aop,那就只能使用aspectJ了(不过现在没有用spring的项目应该挺少的吧。。。)
4)aspectJ由于是编译期进行的织入,性能会比spring好一点

Spring Aop的三种实现方式

  • 基于接口的方式
  • 基于注解的方式
  • Xml配置的方式

解析切面类

AnnotationAwareAspectJAutoProxxyCreator

这个bean的后置处理器,实现了InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation()方法,也就是bean后置处理器的9次调用中的第一次,就是在这里完成Advisor的解析填充工作

后续BeanPostProcessor的postProcessAfterInitialization()方法中,就会循环遍历这些填充好的Advisor,一个个匹配,然后创建bean的动态代理

Spring源码系列-Spring AOP_第32张图片

可以看到这里,会执行9次Bean后置处理器的第一次调用,也就是在第一次调用这里会进行解析切面 

Spring源码系列-Spring AOP_第33张图片

Spring源码系列-Spring AOP_第34张图片

解析切面是很耗费性能的,所以需要保证只解析一次,所以就创建了缓存,解析好后就缓存起来,下次就不解析了 

Spring源码系列-Spring AOP_第35张图片

如果是这几个类就不需要解析了,因为我们只解析标注了@Aspect的类,要做的工作只是把 @Aspect的类中的一个个通知,解析成一个个的Advisor存入list中

Spring源码系列-Spring AOP_第36张图片

Spring源码系列-Spring AOP_第37张图片

Spring源码系列-Spring AOP_第38张图片

Spring代码的对老代码的兼容性

Spring源码系列-Spring AOP_第39张图片

这段代码就是为了兼容老的代码(向下兼容),因为当前的注解实现的aop的方式,是不会往ioc容器中手动显示的注入Advisor接口的实现类对象的,所以这里就会找不到。如果不是为了兼容老代码,那么这段84行开始的代码,就可以删除了

先去容器中找有没有实现了Advisor接口的类,因为我们当前都已经使用全注解的方式了,所以自然就在ioc容器中找不到实现了Advisor接口的类了

Spring源码系列-Spring AOP_第40张图片

上面传统的aop的实现方式时,是自己手动往ioc容器中注入一个Advisor类型的bean,如果这个时候,ioc容器中就能找到实现了Advisor接口的类了

Spring源码系列-Spring AOP_第41张图片

这里,就开始判断ioc中的bean,哪些bean有@Aspect注解,若果是切面类则把里面的所有通知都解析成一个个Advisor

Spring源码系列-Spring AOP_第42张图片

判断当前类,是不是切面类

Spring源码系列-Spring AOP_第43张图片

使用缓存,每个切面类的类名作为key,切面类下的List作为value

切面类

Spring源码系列-Spring AOP_第44张图片

Spring源码系列-Spring AOP_第45张图片

会扫描所有标注了@Aspect注解的Bean,解析切面类中的每一个方法,只要这个bean的哪个方法上标注了@Before,@After等注解,就生成一个对应的Advisor

cglib动态代理和jdk动态代理

Spring源码系列-Spring AOP_第46张图片

cglib生成的动态代理对象,调用自身的方法,也是要经过动态代理的逻辑

jdk动态代理对象的方法内部,调用自己的另一个方法,则不会走动态代理增强的逻辑 

jdk和cglib代理现在都是修改的字节码,所以现在两者性能方面都是差不多的

创建动态代理

Spring源码系列-Spring AOP_第47张图片

doCreateBean()内每个bean都会经历实例化、属性注入、初始化,在初始化后就会调用一堆的后置处理器的postProcessAfterInitialization()方法,当调用到AbstractAutoProxyCreator这个后置处理器的postProcessAfterInitialization()方法时,这个方法内部就会拿到前面解析并缓存好的所有的切面类的所有Advisors,然后循环遍历Advisors是否能匹配上当前bean,如果能匹配上就开始创建当前bean的动态代理对象,创建好的动态代理对象,就会交给ioc容器

Spring源码系列-Spring AOP_第48张图片

调用代理方法

Spring源码系列-Spring AOP_第49张图片

Spring源码系列-Spring AOP_第50张图片

Spring源码系列-Spring AOP_第51张图片

aop的运行原理就是,先把切面所有通知变成统一的Advisor,然后通过AspectJ的表达式匹配算法,判断出当前bean与哪些Advisor匹配,将这些匹配的Advisor们保存起来,在创建jdk动态代理对象时,将这些匹配的匹配的Advisor们,传入ReflctiveMethodInvocation中,然后就是通过递归调用,依次调用各个Advice,最后调到目标方法,然后再一步步的返回

Spring源码系列-Spring AOP_第52张图片

Spring源码系列-Spring AOP_第53张图片

不管是不是有异常抛出,后置通知都是会执行的,因为后置通知是写在finally 中的

疑问:

上面的流程,好像还是只到了类级别的控制,没有到方法级别的控制。

Spring源码系列-Spring AOP_第54张图片

jdk动态代理内一个方法直接调用另一个方法,是不会触发另一个方法的动态代理逻辑的,我们只有通过这种方法,先拿到动态代理对象,然后在调用动态代理的方法

Spring源码系列-Spring AOP_第55张图片

Spring源码系列-Spring AOP_第56张图片



 

你可能感兴趣的:(spring)