Spring源码学习笔记13——总结篇, 从IOC到AOP
零丶序言
在《Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点》中,我们总结了Spring IOC部分的知识,为了更好的给群里的伙伴们分享Spring AOP的知识,遂有了这篇文章,这篇文章将从IOC聊到AOP,其中IOC不会那么细致,重点还是在AOP。
一丶引入
1.AOP概述
AOP
为Aspect Oriented Programming
的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 是一种新的模块化机制,用来描述分散在对象,类,或函数中的横切关注点
AOP的优点:
分离关注点使解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑代码中不在含义针对特定领域的代码调用,业务逻辑同特定领域问题的关系通过切面封装,维护,这样原本分散在整个应用程序中的变动可以很好地管理起来
2.何为横切关注点
如上图中我们有三个Service处理不同的业务逻辑,但是在三个服务中有一些和业务无关的逻辑横切在业务代码中,比如上面的权限校验,事务,耗时统计等。
为了解决横切关注点和业务逻辑的耦合,以及其分散不好管理的问题,AOP营运而生。AOP解决问题的方式有三种:
- 编译期AOP:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect定义切面 + aspecj特殊编译器来实现编译期AOP;
- 类加载器AOP:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
- 运行期AOP:目标对象和切面都是普通Java类,通过
JVM的动态代理功能
或者第三方库实现运行期
动态织入。
其中Spring AOP就是基于运行期AOP。
3.Spring AOP
AOP 并不是Spring框架提出的概念,而是spring将AOP结合到自己的IOC框架中,在spring 的bean的声明周期中实现运行期AOP增强。代表的有Spring事务,Spring @Async,Spring @Cacheable等功能。
二丶Spring Bean的生命周期
上面我们说到,Spring AOP的优势在于其基于Spring IOC在spring bean声明周期中对bean进行增强,从而实现:程序员编写横切逻辑=>Spring 容器启动 => 动态代理AOP=> 丝滑使用。
这一章节我们先简单回顾下Spring bean的生命周期,了解Spring AOP 实现的"抓手doge"
。
1. BeanDefinition的生成
注解 or xml,这是spring中定义bean两种常用方式,两种方式都最终会将bean的定义转化为Bean的定义信息BeanDefinition。
2.bean的实例化
BeanDefinition 将指导bean的生命周期,首先是实例化,即将BeanDefinition 中记录的类实例化成对象,
其中根据Bean 作用域不同的有所不同
- 单例bean且非懒加载:spring 容器启动的时候即初始化
- 非单例bean:由对应的Scope控制生命周期,但是实例化和单例一样
另外spring中具备多种实例化bean的方式
FactoryBean,调用getObject生成,一般是懒加载的,但是SmartFactoryBean#isEagerInit = true 且如果是单例的话,那么在spring启动的时候就会调用getObject进行生成
非FactoryBean:
- 反射调用构造方法or工厂方法 生成
- CGLIB 生成子类进行生成(当使用了spring的MethodOverride后)
3.bean属性填充
bean的属性填充笔者不那么专业的分为三种
有参构造方法 or 工厂方法实例化bean的时候,参数会被spring 进行依赖注入,从而我们可以将注入的bean设置到属性上
xml定义的bean使用
<property>
指定属性填充,orBeanDefinition#setPropertyValues
指定属性填充,这种方式将在populateBean
进行属性填充GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
MutablePropertyValues propertyValues = new MutablePropertyValues();
// 指定属性a 使用 名称为b 进行填充
propertyValues.add("a",new RuntimeBeanReference("b"));
genericBeanDefinition.setPropertyValues(propertyValues);
@Resource 或者@Autowired注解定义的属性注入
二者会由不同的BeanPostProcessor(Bean的后置处理器)进行处理
@Resource由 CommonAnnotationBeanPostProcessor进行处理
@Autowired由 AutowiredAnnotationBeanPostProcessor进行处理
4.bean的初始化
- 如果实现了Aware那么进行回调
- BeanNameAware,BeanClassLoaderAware,BeanFactoryAware由BeanFactory#invokeAwareMethods方法直接触发
- EnvironmentAware,ApplicationContextAware等接口,由ApplicationContextAwareProcessor这个BeanPostProcessor触发
- 回调初始化方法
- 如果实现了InitializingBean,那么回调afterPropertiesSet
- @PostConstruct 注解标注的方法被反射回调
- 如果BeanDefinition中定义了initMethodName(例如@Bean注解指定初始化方法,xml 声明init-method)
5.Bean的销毁
- @PreDestroy 注解修饰的方法被反射回调
- 实现了DisposableBean接口,那么回调destroy方法
三丶重要的扩展点BeanPostProcessor&AOP 和IOC的结合点
BeanPostProcessor 中文拙劣的翻译为Bean后置处理器,其中这个后置我没太多理解,此处理器贯穿Bean声明周期,每一个Bean都要经过它的处理
图中没有体现SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference这个在循环依赖中使用到的方法。
这里我们不细聊每一个BeanPostProcessor的方法,着重说一下postProcessAfterInitialization这个方法,此方法在Bean完成初始化后将被回调,其出参是一个Object,如果返回值不为null,那么将由返回的对象替换掉原bean对象。
由此我们可以在postProcessAfterInitialization对原有bean进行AOP增强,并最终被加入Spring BeanFactory中去!
这就是Spring AOP 和IOC的结合点(ps:postProcessBeforeInstantiation,getEarlyBeanReference也由结合的逻辑,postProcessBeforeInstantiation需要我们配置对应的TargetSourceCreator才会生效,getEarlyBeanReference在产生循环依赖的时候会被调用,实现半成品对象也进行AOP增强。循环依赖指路:Spring源码学习笔记8——Spring是如何解决循环依赖的 - Cuzzz - 博客园 (cnblogs.com))
四丶AOP中几个关键的概念
在学习Spring AOP实现原理前,我们先学习下AOP中的几个概念。
1. Advice 通知
定义在连接点做什么,为切面增强提供植入接口。描述Spring AOP围绕方法调而注入的切面行为
2.Pointcut切入点
切点决定Advice通知应该作用在哪个连接点,也就是通过Poincut来定义需要增强的方法集合,这些集合可以按照一定规则来完成,这种情况下,Pointcut意味着标识方法(比如事务切面定义了事务注解方法上生效)切入点是一些列织入逻辑代码的连接点集合
3.Advisor通知器
整合Advice 和 Pointcut,定义应该在哪个关注点使用什么通知进行增强。
4.Joint point连接点
表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point
五丶实现AOP功能的BeanPostProcessor
上面介绍了Spring IOC 和 AOP的结合点和AOP中几个关键的概念,但是具体是如何结合的?下面介绍和AOP功能密切的几个类
注意看AnnotationAwareAspectJAutoProxyCreator
是一个BeanPostProcessor
,从而在Spring 回调postProcessAfterInitialization
对bean进行代理的增强,并且它实现了SmartInstantiationAwareBeanPostProcessor
Spring容器创建bean的时候如果出现了循环依赖那么会调用到getEarlyBeanReference
,在这个方法里面同样也会进行aop的增强
AbstractAutoProxyCreator
实现了SmartInstantiationAwareBeanPostProcessor
是一个bean后置处理器,使用 AOP 代理包装每个符合条件的 bean,在调用 bean 本身之前委托给指定的拦截器,AOP代理发生的地方。AbstractAdvisorAutoProxyCreator
为了每一个Bean找到合适的
Advisor
并且进行,如果Advisor
标注了@Order
或者说实现了Ordered
接口那么会进行排序。AspectJAwareAdvisorAutoProxyCreator
AbstractAdvisorAutoProxyCreator
子类,它支持AspectJ表达式。AnnotationAwareAspectJAutoProxyCreator
AspectJAwareAdvisorAutoProxyCreator
的子类,会将容器中标注了@AspectJ
注解的类解析成Advisor
(整合Advice 和 Pointcut,定义应该使用哪个通知器并在哪个关注点使用它)
六丶Spring AOP代理源码
1.AbstractAutoProxyCreator#postProcessAfterInitialization 在bean初始化后进行代理
其中earlyProxyReferences记录了被代理的早期对象,在下面代码中实现了被代理对象不会再此被代理
2.AbstractAutoProxyCreator#wrapIfNecessary创建代理
其中shouldSkip
被AspectJAwareAdvisorAutoProxyCreator
重写,如果Advisor
是AspectJPointcutAdvisor
并且切面名称和bean名称相同那么会跳过,这保证了@Aspect
标注的类,生成的实例对象不会被代理
2.1.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean找到所有合适的advice 和advisor
findCandidateAdvisors
方法会找到容器中所以的Advisor
类型的bean,AnnotationAwareAspectJAutoProxyCreator
进行了重写,它还会把所以标注了@Aspect
注解的bean中的增强逻辑封装成Advisor
findAdvisorsThatCanApply
这个方法内部逻辑基本上就是调用PointcutAdvisor
获取类过滤器,方法匹配器进行匹配。- sortAdvisors 这里默认是通过@Order注解,或者Ordered接口进行排序,但是
AspectJAwareAdvisorAutoProxyCreator
进行了重写,因为它需要对同一个标注@Aspect切面里面的前置后置等进行排序
2.2AbstractAutoProxyCreator#createProxy创建代理对象
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());
}
可以看到最终使用ProxyFactory.getProxy方法进行动态代理
2.2.1 ProxyFactory是如何创建代理对象的
这里生成的AopProxy 才是负责生成代理对象的,其中spring内置了两种策略——JDK动态代理和CGLIB动态代理。
只有设置了需要代理目标类,或者说没有指定代理的接口,且代理目标类不是接口,不是lambda,不是已经被JDK动态代理后的类,那么才会使用CGLIB进行动态代理。
其中JdkDynamicAopProxy
,还实现了InvocationHandler
。
Jdk动态代理
生成代理对象
Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this)
,这里的this便是自己。CGLIB动态代理
设置CallBack
首先new 一个
Enhancer
设置父类为被代理对象的类型,这里会把讲Aop的逻辑转变为一个DynamicAdvisedInterceptor
,equals和hashCode方法也有对应的callBack注意这里的
MethodInterceptor
是org.springframework.cglib.proxy.MethodInterceptor
,其中的intercept 方法的逻辑和JDK动态代理的invoke类似,都是链式调用。代理对象生成
使用Enhancer进行增强
3.AOP代理逻辑的执行
以Jdk动态代理为例子,我们看下增强后对象方法的调用,是怎么进入到Advice中代码逻辑的
3.1 前置逻辑
这里解释了为什么AopContext可以拿到当前代理后的对象!
3.2 方法执行
3.2.1 创建拦截器链
首先有一层cache,避免每次都重复创建
这一步会将Advisor转换成MethodInterceptor,转换的过程使用了AdvisorAdapterRegistry
可以看到它可以将Advisor和MethodInterceptor进行互相转换,其中转换逻辑如下,使用了责任链+适配器模式
以下是三种类型的Adapter和对应转换后的MethodInterceptor
根据这些MethodInterceptor的名称可猜测出它们不同的执行时机!
3.2.1 ReflectiveMethodInvocation#proceed(拦截逻辑+被代理对象方法执行)
ReflectiveMethodInvocation的执行和J2EE中的FilterChain异曲同工之妙。
所谓的Joinpoint执行其实就是反射调用被代理对象的方法
4.AnnotationAwareAspectJAutoProxyCreator(Spring中@Aspect是如何生效)
上面我们讲了其父类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 AOP的原理,可以看到Spring AOP实现的原理没有那么神秘,其实就是BeanPostProcessor + 动态代理 + 反射,但是其和Spring IOC结合密切,这正是其”技术壁垒(doge)“,其中也有一些优秀的设计——工厂+代理+责任链+策略+门面设计模式,这些设计模式是真好用呀。
那么事务,@Retryable,@Cacheable,@Async是怎么实现的昵?----它们由各自的Advisor去实现。
这也是Spring AOP的优点之一,后续横切逻辑只需要开发者编写对应的Adivor即可,实现了横切逻辑和业务逻辑的解耦合
。
Spring源码学习笔记13——总结篇, 从IOC到AOP的更多相关文章
- Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点
Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...
- Spring源码学习笔记9——构造器注入及其循环依赖
Spring源码学习笔记9--构造器注入及其循环依赖 一丶前言 前面我们分析了spring基于字段的和基于set方法注入的原理,但是没有分析第二常用的注入方式(构造器注入)(第一常用字段注入),并且在 ...
- Spring 源码学习笔记10——Spring AOP
Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...
- Spring 源码学习笔记11——Spring事务
Spring 源码学习笔记11--Spring事务 Spring事务是基于Spring Aop的扩展 AOP的知识参见<Spring 源码学习笔记10--Spring AOP> 图片参考了 ...
- spring源码学习笔记之容器的基本实现(一)
前言 最近学习了<<Spring源码深度解析>>受益匪浅,本博客是对学习内容的一个总结.分享,方便日后自己复习或与一同学习的小伙伴一起探讨之用. 建议与源码配合使用,效果更嘉, ...
- BIND9源码学习笔记1---gdb调试篇
学习bind9源码之前,首先要知道如何用gdb来调试bind.BIND9的源码我是先看代码弄懂它的架构,像什么event-drive,epoll等, 再去看它的业务流程.看业务流程的时候要追踪它的数据 ...
- Spring源码学习笔记之bean标签属性介绍及作用
传统的Spring项目, xml 配置bean在代码中是经常遇到, 那么在配置bean的时候,这些属性的作用是什么呢? 虽然说现在boot项目兴起,基于xml配置的少了很多, 但是如果能够了解这些标签 ...
- Spring源码学习笔记之基于ClassPathXmlApplicationContext进行bean标签解析
bean 标签在spring的配置文件中, 是非常重要的一个标签, 即便现在boot项目比较流行, 但是还是有必要理解bean标签的解析流程,有助于我们进行 基于注解配置, 也知道各个标签的作用,以及 ...
- Spring源码学习笔记1
1.Spring中最核心的两个类 1)DefaultListableBeanFactory XmlBeanFactory继承自DefaultListableBeanFactory,DefaultLis ...
- Spring源码学习笔记2
1.默认标签的解析 对四种不同标签的解析 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate dele ...
随机推荐
- 2021-05-26:给定一个char[][] matrix
2021-05-26:给定一个char[][] matrix,也就是char类型的二维数组,再给定一个字符串word,可以从任何一个某个位置出发,可以走上下左右,能不能找到word?char[][] ...
- centos安装Vue
一直以来,有关LINUX的系统安装包,都是比较随意,直接使用yum进行或者apt-get 安装 标准安装流程是什么的呢.我们通过centos安装Vue进行展示 1 首先下载安装nodejs , htt ...
- CF1583H Omkar and Tours 题解
题意: 给定一个 \(n\) 个点的树,每条边有权值 \(t\) 和 \(c\).一条路径的权值为所经过节点的 \(\max(c)\). 每个点有权值 \(e\). 给出 \(q\) 个询问,每次询问 ...
- 安装指定版本的mysql(mysql5.7)
安装指定版本的mysql(mysql5.7) 目标:解决需求,安装mysql5.7 前言: 安装软件的三种方式: rpm 安装 源代码编译安装 yum仓库安装 本地光盘 阿里云yum源 自建yum仓库 ...
- Java 实战介绍 Cookie 和 Session 的区别
HTTP 是一种不保存状态的协议,即无状态协议,HTTP 协议不会保存请求和响应之间的通信状态,协议对于发送过的请求和响应都不会做持久化处理. 无状态协议减少了对服务压力,如果一个服务器需要处理百万级 ...
- 【HarmonyOS】如何获取公共目录的图片等文件(API7 FA模型JS语言)
[关键字] API7.JS.公共目录.@ohos.multimedia.mediaLibrary [前言] 在使用API7 JS开发HarmonyOS应用时,需要获取非应用目录即内部存储公共目录下 ...
- 使用python发送sip协议的OPTIONS
环境:Windows10_x64 Python版本 :3.9.2 sip协议提供了OPTIONS请求方法可用于探测对端状态,今天记录下Windows10环境下使用python3.9简单实现sip ...
- 深入浅出synchronized的原理与源码
深入浅出synchronized的原理与源码 1.java对象头关于锁的标识 1.对象头 // 32 bits: // -------- // hash:25 ------------>| ag ...
- 驱动开发:应用DeviceIoContro模板精讲
在笔者上一篇文章<驱动开发:应用DeviceIoContro开发模板>简单为大家介绍了如何使用DeviceIoContro模板快速创建一个驱动开发通信案例,但是该案例过于简单也无法独立加载 ...
- 使用C#编写.NET分析器-第二部分
译者注 这是在Datadog公司任职的Kevin Gosse大佬使用C#编写.NET分析器的系列文章之一,在国内只有很少很少的人了解和研究.NET分析器,它常被用于APM(应用性能诊断).IDE.诊断 ...