Spring源码——AOP实现原理
引言
Spring AOP(Aspect Orient Programming),AOP翻译过来就是面向切面编程,它体现的是一种编程思想,是对面向对象编程(OOP)的一种补充。
在实际业务开发过程中,有一些代码,跟业务没有任何关系,但在很多地方又会用到,比如:记录日志、计算执行时间、事务、权限验证等等,这些代码跟我们实际业务没有一丁点关系,但在许多的地方又会用到,我们传统的方式,肯定是通过复制粘贴把它粘进需要用到的地方,但这样做,会对原本的业务代码块造成很高侵入性,使代码的阅读性、可维护性变得很糟糕,所以就延伸出来了AOP的概念,它不是针对于某个编程语言、某段代码、而是体现了一种编程思想,通过切面的方式将非业务代码切入到核心业务处理流程中,减少对业务代码的修改,降低对业务代码的侵入程度。
Spring作为一个Bean对象的管理框架,提供了AOP功能集成的扩展接口(比如:事务)。
AOP动态代理
AOP是借助了动态代理的实现机制,通过为需要被切入的对象生成动态代理的方式来实现的。动态代理相关原理请参考笔者另一篇博客———设计模式——动态代理
AOP相关术语
AOP用到的一些专业术语
1. 通知(Advice)
AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
2. 连接点(join point)
连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
3. 切点(PointCut)
可以插入增强处理的连接点。
4. 切面(Aspect)
切面是通知和切点的结合。
5. 引入(Introduction)
引入允许我们向现有的类添加新的方法或者属性。
6. 织入(Weaving)
将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
Spring AOP
在Spring对Bean对象的创建过程中,会将已实例化但尚未初始化的 Bean对象(半成品),最先放入到三级缓存(singletonFactories)中,而在放入的时候,并不是放入的一个实例对象那么简单,它是将获取这个半成品的工厂方法给放入了三级缓存中,然后再对这个半成品进行属性填充、初始化等操作,这里也是解决循环依赖的核心所在,在后续代码执行中,就可以通过beanName在三级缓存中,获取到这个ObjectFactory对象,通过调用ObjectFactory的getObject方法,获取之前创建的半成品。
而对于AOP对象的动态代理对象的创建,也就是在这个从三级缓存(半成品) 放入 二级缓存(成品的动态代理)过程中的进行处理的。
以下doCreateBean方法里省略了部分代码
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException{
...
// 判断当前bean是否需要提前曝光:单例&允许循环依赖&当前bean正在创建中,检测循环依赖
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 为避免后期循环依赖,可以在bean初始化完成前将创建实例的ObjectFactory加入工厂
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
...
allowCircularReferences这个值默认等于true。以上代码调用了addSingletonFactory方法,实际上它是向三级缓存中添加了一个lambd表达式
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
// 使用singletonObjects进行加锁,保证线程安全
synchronized (this.singletonObjects) {
// 如果单例对象的高速缓存【beam名称-bean实例】没有beanName的对象
if (!this.singletonObjects.containsKey(beanName)) {
// 将beanName,singletonFactory放到单例工厂的缓存【bean名称 - ObjectFactory】
this.singletonFactories.put(beanName, singletonFactory);
// 从早期单例对象的高速缓存【bean名称-bean实例】 移除beanName的相关缓存对象
this.earlySingletonObjects.remove(beanName);
// 将beanName添加已注册的单例集中
this.registeredSingletons.add(beanName);
}
}
}
lambd表达式里面的方法,getEarlyBeanReference(beanName, mbd, bean)方法源码
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
// 默认最终公开的对象是bean,通过createBeanInstance创建出来的普通对象
Object exposedObject = bean;
// mbd的systhetic属性:设置此bean定义是否是"synthetic",一般是指只有AOP相关的pointCut配置或者Advice配置才会将 synthetic设置为true
// 如果mdb不是synthetic且此工厂拥有InstantiationAwareBeanPostProcessor
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
// 遍历工厂内的所有后处理器
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// 如果bp是SmartInstantiationAwareBeanPostProcessor实例
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
// 让exposedObject经过每个SmartInstantiationAwareBeanPostProcessor的包装
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
// 返回最终经过层次包装后的对象
return exposedObject;
}
三级缓存中添加的是一个获取对象的lambd表达式,所以这个表达式只是添加进去了,并不会立刻运行,以上就是lambd表达式里面的内容
在lambd表达式里面,我们看到SmartInstantiationAwareBeanPostProcessor这样的一个接口,它属性BPP。
对于每个Bean对象都会遍历BPP,如果是BPP是SmartInstantiationAwareBeanPostProcessor的实现类,就会运行它的getEarlyBeanReference(exposedObject, beanName)方法。
而对于开启了AOP注解(@EnableAspectJAutoProxy)功能的项目,会添加一个AnnotationAwareAspectJAutoProxyCreator,它就属性SmartInstantiationAwareBeanPostProcessor(BPP)
看到这里,大家就应该明白了,AOP功能也是通过对BPP的扩展来实现的。
AnnotationAwareAspectJAutoProxyCreator继承了父类AbstractAutoProxyCreator中的getEarlyBeanReference方法
/**
* 放到集合中,然后判断要不要包装,其实就是在循环依赖注入属性的时候如果有AOP代理的话,也会进行代理,然后返回
* @param bean the raw bean instance
* @param beanName the name of the bean
* @return
*/
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
核心代码wrapIfNecessary(bean, beanName, cacheKey),到此,我们大致明白了,AOP的基本处理流程了(其实因为AnnotationAwareAspectJAutoProxyCreator属于BPP,在执行BPP的后置处理器有时候也有机会执行wrapIfNecessary方法,并返回代理对象)
/**
* 先判断是否已经处理过,是否需要跳过,跳过的话直接就放进advisedBeans里,表示不进行代理,如果这个bean处理过了,获取通知拦截器,然后开始进行代理
*
* Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @param cacheKey the cache key for metadata access
* @return a proxy wrapping the bean, or the raw bean instance as-is
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 如果已经处理过,直接返回
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
// 这里advisedBeans缓存了已经进行了代理的bean,如果缓存中存在,则可以直接返回
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
// 这里isInfrastructureClass()用于判断当前bean是否为Spring系统自带的bean,自带的bean是
// 不用进行代理的;shouldSkip()则用于判断当前bean是否应该被略过
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
// 对当前bean进行缓存
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
// 获取当前bean的Advices和Advisors
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// 对当前bean的代理状态进行缓存
if (specificInterceptors != DO_NOT_PROXY) {
// 对当前bean的代理状态进行缓存
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 根据获取到的Advices和Advisors为当前bean生成代理对象
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
// 缓存生成的代理bean的类型,并且返回生成的代理bean
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
Spring AOP生成代理类时JDK代理和CGLIB代理的选择
/**
* 真正的创建代理,判断一些列条件,有自定义的接口的就会创建jdk代理,否则就是cglib
* @param config the AOP configuration in the form of an
* AdvisedSupport object
* @return
* @throws AopConfigException
*/
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 这段代码用来判断选择哪种创建代理对象的方式
// config.isOptimize() 是否对代理类的生成使用策略优化 其作用是和isProxyTargetClass是一样的 默认为false
// config.isProxyTargetClass() 是否使用Cglib的方式创建代理对象 默认为false
// hasNoUserSuppliedProxyInterfaces目标类是否有接口存在 且只有一个接口的时候接口类型不是SpringProxy类型
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
// 上面的三个方法有一个为true的话,则进入到这里
// 从AdvisedSupport中获取目标类 类对象
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判断目标类是否是接口 如果目标类是接口的话,则还是使用JDK的方式生成代理对象
// 如果目标类是Proxy类型 则还是使用JDK的方式生成代理对象
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 配置了使用Cglib进行动态代理或者目标类没有接口,那么使用Cglib的方式创建代理对象
return new ObjenesisCglibAopProxy(config);
}
else {
// 使用JDK的提供的代理方式生成代理对象
return new JdkDynamicAopProxy(config);
}
}
Spring AOP生成动态代理大致流程如下
两种代理方式执行逻辑
AOP执行逻辑
AOP代理方式有两种:JDK代理 和 CGLIB代理
JdkDynamicAopProxy
JdkDynamicAopProxy类是实现了反射中的InvocationHandler接口
所以调用过程中的核心执行方法在invoke方法中
invoke方法中核心代码块
// We need to create a method invocation...
// 将拦截器封装在ReflectiveMethodInvocation,以便于使用其proceed进行处理
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
// 执行拦截器链
retVal = invocation.proceed();
看得出来,它的执行是交给一个MethodInvocation去执行的,而ReflectiveMethodInvocation是一个执行器链,里面根据我们的配置有可能包含了以下五种通知
采用责任链模式对自定义配置的五种通知进行依次调用
ObjenesisCglibAopProxy
Spring源码——AOP实现原理的更多相关文章
- spring源码-aop源码-5.1
一.aop的源码部分还是有点复杂的,但是为了更好的理解,我这里会省去很多不必要的逻辑实现过程.主要方向还是更好的理解整体代码的实现过程. 二.说明重点:aop的过程主要过程有两点:第一点,发现正确和适 ...
- Spring源码系列 — 注解原理
前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...
- Spring源码之@Configuration原理
总结 @Configuration注解的Bean,在BeanDefinition加载注册到IOC容器之后,进行postProcessBeanFactory处理时会进行CGLIB动态代理 将@Prope ...
- Spring源码-AOP部分-Spring是如何对bean实现AOP代理的
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 历史文章 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] S ...
- Spring源码 03 IOC原理
参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...
- 4.1 Spring源码 --- 监听器的原理
目标: 1. 监听器如何使用 2. 监听器的原理 3. 监听器的类型 4. 多播器的概念和作用 5. 接口类型的监听器是如何注册的? 6. 注解类型的监听器和如何注册的? 7. 如果想在所有的bean ...
- Spring源码:IOC原理解析(一)
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! IOC(Inversion of Control),即控制反转,意思是将对象的创建和依赖关系交给第三方容器处理,我们要用的时候告诉容器我们 ...
- Spring源码:IOC原理解析(二)
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 接着上一章节的内容,我们来分析当new一个FileSystemXmlApplicationContext对象的时候,spring到底做了那 ...
- spring源码-aop动态代理-5.3
一.动态代理,这是一个很强大的东西哦.研发过程中我们会常用很多业务类,但是存在一个问题.如何在不修改源码逻辑的情况下,加入自己的相关逻辑.比如异常处理,日志记录等! 二.Java动态代理的两种方式JD ...
- spring源码-aop增强-5.2
一.aop增强就是针对于不同的切面进行的相关增强,目的当然是更好的支持相关应用和解耦. 二.默认的aop增强类有AspectJMethodBeforeAdvice.AspectJMethodBefor ...
随机推荐
- 如何落地云原生DevOps?
简介: 什么是云原生DevOps?在阿里内部有怎样的实践?企业又该如何落地?阿里云云效专家团队提出了下一代精益产品开发方法体系--ALPD,提供了系统的云原生DevOps落地的方法支撑,帮助企业渐进式 ...
- 一图速览 | DTCC 2021大会,阿里云数据库技术大咖都聊了些什么?
简介: 3天9场干货分享,快来收藏吧! 10月18日~10月20日, 由国内知名IT技术社区主办的数据库技术交流盛会--DTCC 2021 (第十一届中国数据库技术大会)在京圆满落幕.大会以&quo ...
- [Go] 选择 Beego 的三个理由
1. 项目支持角度较其它框架考虑的多一些,比如:目录结构的简单约定,内置项目配置读取,内置bee脚手架,热重载特性 等. (实际这些 feature 都可以找到 golang 专精的组件引入起来,效果 ...
- WPF 已知问题 Separator 无法应用 ContextMenu 定义的默认样式
本文记录一个 WPF 已知问题,在 ContextMenu 的 Resources 里定义 Separator 的默认样式,在 ContextMenu 里面的 Separator 将应用不上,或者说不 ...
- Niginx中Vue Router 历史(history)模式的配置
快速配置 将build后的文件直接丢到niginx目录下的html文件夹中,然后配置nginx.conf,就可以在快速的实现niginxhistory模式的配置了. location /{ # 可使用 ...
- Docker的基本命令
1.docker使用的优点 1.更快速的交付和部署 对于开发和运维人员来说,最希望的是保持所有环境一致,这样不会导致,开发在自己的环境里程序运行正常而运维跑的服务器环境里就不正常:对于运维来说,可以使 ...
- easyExcel多行表头设定不同样式和特定单元格设定样式的实现
前言 有个需求,需要设置Excel导出的样式,样式如下图所示,有三个表头行,第一个表头行需要加粗和灰色背景,另外两个表头行使用另外的样式,并且当测试结果单元格出现x或者未通过的时候,设置其为红色字体. ...
- NVCC编译选项含义解析
NVCC编译 nvcc 是cuda程序的编译器. 1. 编译阶段 用于指定编译阶段最基本的编译参数. -c: 同gcc,只预处理.编译和汇编为.o文件,不link -lib:生成一个库文件,windo ...
- USB3.0与Type-C接口的关系
USB全称为Universal Serial Bus,翻译过来就是通用串行总线,是连接计算机与外部设备的一种串口总线标准.USB的发展经历了一下阶段: USB1.0:1.5Mbps(192KB/s)低 ...
- NSThread的isEexcuting和isFinish什么时候被设置
NSThread的isExecuting在进入-[NSThread main]函数之前就已经被设置成YES; NSThread的isFinished在执行+[NSThread exit]后才被设置成N ...