Spring 源码学习笔记10——Spring AOP
Spring 源码学习笔记10——Spring AOP
参考书籍《Spring技术内幕》Spring AOP的实现章节
书有点老,但是里面一些概念还是总结比较到位
源码基于Spring-aop 5.3.22 可能和旧版本有所差异但是大体逻辑一致
一丶AOP概述
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 是一种新的模块化机制,用来描述分散在对象,类,或函数中的横切关注点,分离关注点使解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑代码中不在含义针对特定领域的代码调用,业务逻辑同特定领域问题的关系通过切面封装,维护,这样原本分散在整个应用程序中的变动可以很好地管理起来。
用人话说就是,通过切面完成特定逻辑(事务,入参出参日志等)
可以和业务逻辑(crud)抽离开,便于维护
1. Advice 通知
定义在连接点做什么,为切面增强提供植入接口。描述Spring AOP围绕方法调而注入的切面行为
2.Pointcut切入点
切点决定Advice通知应该作用在哪个连接点,也就是通过Poincut来定义需要增强的方法集合,这些集合可以按照一定规则来完成,这种情况下,Pointcut意味着标识方法(比如事务切面定义了事务注解方法上生效)切入点是一些列织入逻辑代码的连接点集合
3.Advisor通知器
整合Advice 和 Pointcut,定义应该使用哪个通知器并在哪个关注点使用它。
二丶aop 重要接口和编程体验
我们先抛弃Spring框架,利用Spring aop中存在的工具实现aop增强。
1.基于Advice

Advice接口的实现有AfterAdvice后置通知,Beforeadvice前置通知,MethodInterceptor方法拦截器可以看做是环绕通知。
/**
* 服务类
*/
public static class Service{
public void doSomething(){
System.out.println("service doSomething");
}
}
/***
* 自定义的advice 环绕通知
*/
public static class MyAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("my advice before");
//service 方法执行
Object res = invocation.proceed();
System.out.println("my advice after");
return res;
}
}
public static void main(String[] args) {
//代理工程 用于生成代理对象
ProxyFactory proxyFactory = new ProxyFactory();
//目标对象
Service service = new Service();
//设置需要代理的对象
proxyFactory.setTarget(service);
proxyFactory.addAdvice(new MyAdvice());
//生成代理对象
Service proxyService = (Service) proxyFactory.getProxy();
//代理对象执行
proxyService.doSomething();
}
上述代码执行结果

基于Advice,ProxyFactory成功实现了Aop代理增强
2.基于Advisor

public static void advisorBased(){
//代理工程 用于生成代理对象
ProxyFactory proxyFactory = new ProxyFactory();
//目标对象
Service service = new Service();
//advisor
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
//根据名称匹配方法的pointcut
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
//指定只有doSomething 才增强
pointcut.setMappedName("doSomething");
advisor.setPointcut(pointcut);
advisor.setAdvice(new MyAdvice());
//设置需要代理的对象
proxyFactory.setTarget(service);
proxyFactory.addAdvisor(advisor);
//生成代理对象
Service proxyService = (Service) proxyFactory.getProxy();
//代理对象执行
proxyService.doSomething();
System.out.println();
proxyService.sayHello();
}

最后我们发现只有名称匹配的方法才生效。
Advisor接口具备方法Advice getAdvice()来获取通知。PointcutAdvisor实现了Advisor并且新增方法Pointcut getPointcut()来获取切入点的定义。Pointcut接口定义了两个方法ClassFilter getClassFilter(),MethodMatcher getMethodMatcher()分别是对类和方法的筛选,来决定Advise是不是应该作用于当前类。
三丶ProxyFactory 和 ProxyFactoryBean
1.ProxyFactory
1.1类图

TargetSource 用于获取 AOP 调用的当前“目标”

getTargetClass可以获取被代理对象的类型,getTarget可以获取被代理对象,HotSwappableTargetSource中的swap方法可以替换掉代理对象,Spring aop常用的是SingletonTargetSource它持有了原始的被代理对象。
1.2 proxyFactory是如何创建代理对象的。
public Object getProxy() {
return createAopProxy().getProxy();
}

1.2.1 DefaultAopProxyFactory # createAopProxy

这里生成的AopProxy 才是负责生成代理对象的,其中spring内置了两种策略——JDK动态代理和CGLIB动态代理。
只有设置了需要代理目标类,或者说没有指定代理的接口,且代理目标类不是接口,不是lambda,不是已经被JDK动态代理后的类,那么才会使用CGLIB进行动态代理。
1.2.2 AopPorxy

其中JdkDynamicAopProxy,还实现了InvocationHandler。
Jdk动态代理
生成代理对象
Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this),这里的this便是自己。invoke
首先是对`equals`,`hashCode`的处理,目标对象声明了让目标对象执行,反之调用`JdkDynamicAopProxy`对应的方法
其次是如果配置中设置了暴露代理对象,那么将其放入到`AopContext`中的`ThreadLocal`中
```java
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
```
然后获取当前对象的拦截器链,如果拦截器链为空 那么直接反射调用目标对象的方法。如果存在拦截器链那么new 一个`ReflectiveMethodInvocation`利用反射依次执行。Spring只支持方法拦截器`MethodInterceptor`进行代理增强,对于Advise都会适配成`MethodInterceptor`,Spring采用适配器模式,具体的适配器如下

```java
public interface AdvisorAdapter {
//支持什么Advise被适配
boolean supportsAdvice(Advice advice);
//适配
MethodInterceptor getInterceptor(Advisor advisor);
}
```
自然是Spring遍历每一个Advise责任链模式依次找到`AdvisorAdapter`然后调用适配方法得到一个`MethodInterceptor`,下面是适配成的`MethodInterceptor`。

CGLIB动态代理
设置CallBack
首先new 一个
Enhancer设置父类为被代理对象的类型,这里会把讲Aop的逻辑转变为一个DynamicAdvisedInterceptor,equals和hashCode方法也有对应的callBack
注意这里的
MethodInterceptor是org.springframework.cglib.proxy.MethodInterceptor,其中的intercept 方法的逻辑和JDK动态代理的invoke类似,都是链式调用。
2.ProxyFactoryBean

ProxyFactoryBean创建代理对象的逻辑和ProxyFactory类似,但是ProxyFactoryBean是一个FactoryBean,我们可以利用这一点在bean初始化的时候生成一个代理对象


创建原型对象类似,但是不会判断之前是否创建过,直接无脑创建即可
四丶Spring AOP 和IOC是如何结合起来的
通常我们在使用Spring Aop的时候会在启动类上加一个@EnableAspectJAutoProxy注解,这个注解@Import(AspectJAutoProxyRegistrar.class)导入了AspectJAutoProxyRegistrar,这个类实现了ImportBeanDefinitionRegistrar,Spring容器会调用其registerBeanDefinitions方法为我们注入BeanDefinition,后续会实例化一个AnnotationAwareAspectJAutoProxyCreator类型的bean,它是Spring IOC和AOP结合的关键
1.AnnotationAwareAspectJAutoProxyCreator类结构图

这其中最为关键的必然是AnnotationAwareAspectJAutoProxyCreator是一个BeanPostProcessor,从而在Spring 回调postProcessAfterInitialization对bean进行代理的增强,并且它实现了SmartInstantiationAwareBeanPostProcessor Spring容器创建bean的时候如果出现了循环依赖那么会调用到getEarlyBeanReference,在这个方法里面同样也会进行aop的增强
AbstractAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor是一个bean后置处理器,使用 AOP 代理包装每个符合条件的 bean,在调用 bean 本身之前委托给指定的拦截器,AOP代理发生的地方。AbstractAdvisorAutoProxyCreator为了每一个Bean找到合适的
Advisor并且进行,如果Advisor标注了@Order或者说实现了Ordered接口那么会进行排序。AspectJAwareAdvisorAutoProxyCreatorAbstractAdvisorAutoProxyCreator子类,对一个切面中的多个Advisor进行优先级排序AnnotationAwareAspectJAutoProxyCreatorAspectJAwareAdvisorAutoProxyCreator的子类,会将容器中标注了@AspectJ注解的类解析成Advisor(整合Advice 和 Pointcut,定义应该使用哪个通知器并在哪个关注点使用它)
2.AbstractAutoProxyCreator是如何进行Aop增强的


进行AOP增强的地方其实还有postProcessBeforeInstantiation如果我们自己配置了TargetSourceCreator并且可以为当前bean生成,那么才可能发生aop,这里一般不会进行任何操作。
2.1 wrapIfNecessary

其中shouldSkip被AspectJAwareAdvisorAutoProxyCreator重写,如果Advisor是AspectJPointcutAdvisor并且切面名称和bean名称相同那么会跳过,这应该是我们标注@Aspect的时候需要保证这个类会被Spring加入到容器,所有需要加@Componet所以会过滤掉
2.1.1 如何找到所有合适的advice 和advisor

findCandidateAdvisors方法会找到容器中所以的Advisor类型的bean,AnnotationAwareAspectJAutoProxyCreator进行了重写,它还会把所以标注了@Aspect注解的bean中的增强逻辑封装成AdvisorfindAdvisorsThatCanApply这个方法内部逻辑基本上就是调用PointcutAdvisor获取类过滤器,方法匹配器进行匹配。sortAdvisors 这里默认是通过@Order注解,或者Ordered接口进行排序,但是
AspectJAwareAdvisorAutoProxyCreator进行了重写,因为它需要对同一个标注@Aspect切面里面的前置后置等进行排序
2.1.2 创建代理对象
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
//这里的ProxyTargetClass 来自上面的copyFrom 取决于EnableAspectJAutoProxy注解的proxyTargetClass
//proxyTargetClass 表示是否使用基于CGLIB子类的代理
if (!proxyFactory.isProxyTargetClass()) {
//shouldProxyTargetClass 方法就是去BeanFactory中看当前bean的BeanDefinition中是否存在AutoProxy.PRESERVE_TARGET_CLASS_ATTRIBUTE=trued的attribute,当我们手动注入bean的时候可以使用这个强制让当前bean使用CGLIB增强
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
//获取当前类中非Spring回调(InitializingBean,DisposableBean,Aware)类型的接口,且如果接口的方法大于0,那么会把接口类型加入到proxyFactory中,否则设置ProxyTargetClass(没有接口那么没办法使用JDK动态代理)
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
//主要是把上面找到的advise 适配成Advisor。调用的是advisorAdapterRegistry的wrap方法
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
//这里的targetSource是SingletonTargetSource
proxyFactory.setTargetSource(targetSource);
//留给子类扩展的方法
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
//生成代理对象
return proxyFactory.getProxy(getProxyClassLoader());
}
这里其实和我们上面的Aop编程体验中基于Advisor类似,最后都是AopProxy创建代理对象
2.2 AnnotationAwareAspectJAutoProxyCreator
上面我们讲了其父类AbstractAutoProxyCreator的大体逻辑,AnnotationAwareAspectJAutoProxyCreator会将@Aspect注解类解析成Advisor,下面我们重点看下AnnotationAwareAspectJAutoProxyCreator是怎么将@Aspect注解类解析成Advisor的

这里依赖了BeanFactoryAspectJAdvisorsBuilder,它会遍历所有bean,并调用isAspect方法

然后调用ReflectiveAspectJAdvisorFactory的getAdvisors方法将其适配成多个Advisor,会遍历每一个没有标注@Pointcut的方法,然后获取@Around, @Before, @After, @AfterReturning, @AfterThrowing(如果没有那么直接返回)然后获取value中的内容包装成AspectJExpressionPointcut(AspectJ表达式pointcut),然后包装成InstantiationModelAwarePointcutAdvisorImpl在这个类中会把对应注解的方法封装成对应的AbstractAspectJAdvice的子类

调用对应方法依旧采用反射,其子类在合适的实际进行调用。
Spring 源码学习笔记10——Spring AOP的更多相关文章
- Spring 源码学习笔记11——Spring事务
Spring 源码学习笔记11--Spring事务 Spring事务是基于Spring Aop的扩展 AOP的知识参见<Spring 源码学习笔记10--Spring AOP> 图片参考了 ...
- Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点
Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...
- Spring源码学习笔记9——构造器注入及其循环依赖
Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...
- spring源码学习之路---深入AOP(终)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...
- spring源码学习(一)--AOP初探
LZ以前一直觉得,学习spring源码,起码要把人家的代码整体上通读一遍,现在想想这是很愚蠢的,spring作为一个应用平台,不是那么好研究透彻的,而且也不太可能有人把spring的源码全部清楚的过上 ...
- Spring源码学习(二)AOP
----ProxyFactoryBean这个类,这是AOP使用的入口---- AOP有些特有的概念,如:advisor.advice和pointcut等等,使用或配置起来有点绕,让人感觉有些距离感,其 ...
- spring源码学习笔记之容器的基本实现(一)
前言 最近学习了<<Spring源码深度解析>>受益匪浅,本博客是对学习内容的一个总结.分享,方便日后自己复习或与一同学习的小伙伴一起探讨之用. 建议与源码配合使用,效果更嘉, ...
- Spring源码学习(7)——AOP
我们知道,使用面对对象编程的时候有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志.安全检测等,所以就有了一个对面对对象编程的补充,即面对切面编程(AOP),AOP所关注的方向 ...
- Spring源码学习笔记之bean标签属性介绍及作用
传统的Spring项目, xml 配置bean在代码中是经常遇到, 那么在配置bean的时候,这些属性的作用是什么呢? 虽然说现在boot项目兴起,基于xml配置的少了很多, 但是如果能够了解这些标签 ...
随机推荐
- CentOS7及以下版本安装禅道
由于是CentOS7以及以下系统,禅道已经集成了 Apache Nginx Mysql 服务,不需要我们再次安装搭建,我们只进行解压使用就好: 一.进行下载安装 1.在终端命令中输入以下命令确认系统是 ...
- Wireshark学习笔记(二)取证分析案例详解
@ 目录 练习一:分析用户FTP操作 练习二:邮件读取 练习三:有人在摸鱼? 练习一:分析用户FTP操作 已知抓包文件中包含了用户登录FTP服务器并进行交互的一个过程,你能否通过wireshark分析 ...
- flink 流的合并
flink 流的合并操作 union union只能合并类型相同的数据,合并的结果仍然是DataStream,结果操作与未合并之前一致. public static void main(String[ ...
- OpenCloudOS使用snap安装.NET 6
开源操作系统社区 OpenCloudOS 由腾讯与合作伙伴共同倡议发起,是完全中立.全面开放.安全稳定.高性能的操作系统及生态.OpenCloudOS 沉淀了多家厂商在软件和开源生态的优势,继承了腾讯 ...
- 我大抵是卷上瘾了,横竖睡不着!竟让一个Bug,搞我两次!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言:一个Bug 没想到一个Bug,竟然搞我两次! 我大抵是卷上瘾了,横竖都睡不着,坐起来 ...
- Python-基础知识汇集
1.列表 列表是最常用的Python数据类型,它可以作为一个方括号内的逗号分隔值出现. 列表的数据项不需要具有相同的类型 创建一个列表,只要把逗号分隔的不同的数据项使用方括号括起来即可 代码理解:列表 ...
- P2599 [ZJOI2009]取石子游戏 做题感想
题目链接 前言 发现自己三岁时的题目都不会做. 我发现我真的是菜得真实. 正文 神仙构造,分讨题. 不敢说有构造,但是分讨我只服这道题. 看上去像是一个类似 \(Nim\) 游戏的变种,经过不断猜测结 ...
- 基于Python+Sqlite3实现最简单的CRUD
一.基本描述 使用Python,熟悉sqlite3的基本操作(查插删改),以及基本数据类型.事务(ACID). 准备工作:在sqlite3的官网上下载预编译的sqlite文件(windows) ...
- Go死锁——当Channel遇上Mutex时
背景 用metux lock for循环,在for循环中又 向带缓冲的Channel 写数据时,千万要小心死锁! 最近,我在测试ws长链接网关,平均一个星期会遇到一次服务假死问题,因为并不是所有rou ...
- kubernetes 调度
pod 分配给特定的node节点 目的:在一般业务场景,有些pod需要运行在特定的物理节点上,可以通过kubernetes的nodeSelector.nodeName安排pod到指定的节点上运行. # ...