Spring源码系列-Spring AOP

目录

AOP的用法

注解用法

早期的基于接口的用法

递归实现的责任链模式

简单实现

责任链模式就两个关键点

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

Advisor

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

注解形式的Advisor

解决需要创建多个FactoryBean

纯注解的AOP实现原理

AOP源码解析

AOP入口

@EnableAspectJProxy

AOP中的三大BeanPostProcessor

Spring Aop的三种实现方式

解析切面类

AnnotationAwareAspectJAutoProxxyCreator

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

cglib动态代理和jdk动态代理

创建动态代理

调用代理方法


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

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

f1b9924ed8f644a698e966410bfd0964.png

5a87bc01ec9940d9808400514a6ef01e.png

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

AOP的用法

注解用法

dd1dd54f903a4d8eb7719c80d9939cbb.png

1372797fc65140e7bf27a3b86c61bda5.png

dd020a33df9f46bc93006cfe1c826d80.png

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

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

早期的基于接口的用法

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

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

1f2dcf9c08ed4d338193aa37d9151e5a.png

8ecad0a9928e4213b54df9f5628e7888.png

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

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

0b8477bf25a64055a126b118bc846c64.png

99b0337b5b7043b89d8399716c7eb683.png

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

04ba9e506da141f0aa6aff34fd71f63d.png

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

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

f8ee72ad9263401d86ccd8ef117c933e.png

执行结果:

0a7efc679f8e416e8a1dbd6a41f4c96c.png

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

ProxyFactoryBean#getObject()

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

45cabb7396354248ac00c13f8df26a2b.png

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

递归实现的责任链模式

简单实现

a1de9ec861ac4db8ac228fe0927dd2f5.png

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

e3bcac8e32ca4f65bf3c7708ed556c71.png

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

2118776052f141e38ea3f79c97926b8b.png

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

责任链模式就两个关键点

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

04ba9e506da141f0aa6aff34fd71f63d.png

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

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

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

Advisor

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

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

f81975367ce04cbbb86815407e36c5ff.png

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

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

599dfb1df44a428989ed9d553d5210c0.png

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

执行结果:

9c47dee6a0ad4038aa9deddf722c5394.png

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

f1d560c94fbb4816b868eea171203ca6.png 26c554be24be4ef2bbbe9157d1a9d0dd.png

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

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

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

注解形式的Advisor

804b8222d84a4c538b25ccce9268c30c.png

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

c159413fda0c4a369b3c66c83814828d.png

解决需要创建多个FactoryBean

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

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

bcc121989e1341f19114bc92804219d7.png

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

纯注解的AOP实现原理

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

fa19906c05854ac692265bf1cdb10344.png

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

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

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

82b195c5e1ce4942baa0a54241475e43.png

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

Advisor的PointCut有三种不同的类型

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

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

AOP源码解析

Aop核心三个步骤

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

AOP入口

@EnableAspectJProxy

ff10355b59f24128ba09d8e897ef9ac5.png

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

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

4d4010f976394aa0a13756308c52786b.png

9a3e775d1fa5406ebfabf87a6de63234.png c982f2ba3f004257a78fcb184fe18d81.png

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

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

AOP中的三大BeanPostProcessor

6f38f4fac9f842778d065d7ddf2667c2.png

看这个实现类,实现的三个接口,就是实现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的动态代理

c61e87d3eeea463188f20e69eb0fc4d1.png

ef593e9740ca47579122732805270b98.png

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

9d3444662c4d4f8a97cd7298d4cb6754.png

164c11ccd9c54c9580c2ac4a1cdc9616.png

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

95cfa874dfb24315b31a8ecd6342f0b5.png

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

a047a03015e94055895e38ad0c23e6ee.png

e29ce754f1af4b008a55fead51ca7fac.png

abae4b1d95c548f2bb1843bc9431456d.png

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

17793bcf72fc4ecd83ac5cc2b94c6f23.png

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

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

470d2e8fa7c549839b6f0bef792828b6.png

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

97da8cb49c8a4f41a08df6cc148bd392.png

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

431e4be0b0c64aabb8410e1ce0157bd9.png

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

d3063e8e843047c4b47315c60bab7229.png

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

切面类

b69a90875be54dd18522fec5e6b5618d.png

168219d4c9254de8a8b1e06b77069ebc.png

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

cglib动态代理和jdk动态代理

a6489ed82c83450f8ff59c0188fcdbc9.png

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

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

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

创建动态代理

d8de2f9b36354f6eb0c6266152458e8f.png

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

1b50313052da42d1915b97f2cf214d37.png

调用代理方法

5f5bcfcbfa47429ab243462c73b6ff5a.png

7f6af1b5d2994b31b8621cf3424ec85a.png

88990862fdc247a5b3992a0b4eb3b40e.png

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

f88d2be9d5544834bca033222402131c.png

d1475495942c49d7baeb27e5d1fd1154.png

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

疑问:

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

f5a869ca30ba4071ac81e662f9681284.png

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

651c68fb329347fa8e4b38f774fd6c8b.png

3b56b011575c48ba8720daf3652783b9.png



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值