【Spring】AOP实现原理
注册AOP代理创建器
在平时开发过程中,如果想开启AOP,一般会使用@EnableAspectJAutoProxy注解,这样在启动时,它会向Spring容器注册一个代理创建器用于创建代理对象,AOP使用的是AnnotationAwareAspectJAutoProxyCreator,它实现了SmartInstantiationAwareBeanPostProcessor,从名字中可以看出这是一个Bean后置处理器BeanPostProcessor,BeanPostProcessor是Spring提供的一个扩展点,里面提供了两个方法,分别为postProcessBeforeInitialization(初始化之前)和postProcessAfterInitialization(初始化之后),可以在Bean初始化前后,进行一些操作(比如为Bean设置属性值)。
关于后置处理器的使用可参考:【Spring】BeanPostProcessor后置处理器
- Advisor:对切面的封装,使用了@AspectJ注解的类会被Spring封装成Advisor。
AOP的实现主要在代理创建器的postProcessAfterInitialization方法中:
- postProcessAfterInitialization:在bean初始化之后执行的方法,这时候bean已经实例化完毕,这里会调用wrapIfNecessary方法判断是否有必要为该Bean生成AOP代理对象,如果不需要创建AOP代理对象直接返回即可,反之会获取Advisors,然后创建AOP的代理对象,替换掉原来生成的Bean。
// AnnotationAwareAspectJAutoProxyCreator的父类AbstractAutoProxyCreator中实现
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
/**
* 在bean初始化之后执行的方法
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
// 构建缓存Key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 是否有必要创建AOP代理对象
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
}
AOP代理条件判断
Spring需要知道有哪些类需要进行AOP代理、哪些需要跳过AOP代理,比如我们使用@Aspect标注的切面类,只是一个普通Bean,不需要进行AOP代理,而我们的目标类,就需要进行代理,所以这一步会进行判断。
处于以下情况之一是不需要进行AOP代理的,会跳过:
- 如果是Advice、Pointcut、Advisor、AopInfrastructureBean类本身及其子类则跳过创建,这些是Spring的基础类,不需要进行AOP代理;
- 如果有Aspect注解且不是通过Ajc编译的类,这个就是用@Aspect标注的切面类,也不需要AOP进行代理;
- 这个判断条件的作用同上,只不过个判断主要用于在XML中通过 aop:aspect标签形式来配置切面的情况,Spring会生成一个对应的AspectJPointcutAdvisor,切面本身对应的那个Java类是不需要进代理的,所以添加了一个判断,跳过切面本身对应的那个Java类,在使用注解和使用aop:aspect标签时实现不一样,所以这里又加了一个条件判断;
获取Advisor
对于上述情况的bean会跳过,剩下的Bean需要先获取所有的Advisors,从中找出适用于当前Bean的Advisor,如果查找到表示当前Bean需要进行AOP代理,依旧返回原来的Bean对象即可。
Spring会把使用了@AspectJ注解定义的切面包装成Advisor,判断是否有与当前bean匹配的Advisor,判断方式如下:
根据切点Pointcut的getClassFilter方法对类进行匹配,判断当前Bean的class是否匹配;
根据切点Pointcut获取MethodMatcher方法匹配器,通过MethodMatcher对当前Bean中的每一个方法进行匹配,也就是使用配置的切点表达式对方法进行匹配;
经过这一步处理,如果匹配到了该Bean的Advisor,说明当前Bean需要进行AOP代理,会返回适用于当前Bean的Advisor集合,接下来会为该Bean创建AOP代理对象。
创建AOP代理对象
创建代理对象
前置知识:JDK动态代理,可参考 【Java】JDK动态代理实现原理
Spring提供了两种方式创建代理对象,分别是JDK动态代理和Cglib,使用JDK动态代理需要被代理对象实现接口,否则使用Cglib实现。
以JDK动态代理为例,创建代理对象的过程在JdkDynamicAopProxy中,它实现了InvocationHandler,在通过JDK的动态代理创建对象的时候,需要这个InvocationHandler,通过Proxy的newProxyInstance即可创建AOP代理对象:
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// 生成代理对象,proxiedInterfaces为目标代理类,this为InvocationHandler也就是当前的JdkDynamicAopProxy
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
}
执行目标方法
在创建了AOP代理对象之后,会使用这个代理对象替换掉原来容器中的Bean,开发过程中拿到的Bean就是这个AOP代理对象了,当执行目标方法时,首先会进入到代理对象的invoke方法(JdkDynamicAopProxy中的invoke方法,InvocationHandler中定义了invoke方法,JdkDynamicAopProxy实现了InvocationHandler接口所以会实现这个方法):
对于AOP,在invoke方法中,会先获取目标方法的所有拦截器,Spring会将适用于当前方法的Advisor转为方法拦截器,然后使用责任链模式,对拦截器进行一个个的调用,当然如果当前方法没有对应的拦截器需要执行,直接通过反射执行目标方法即可。
因为拦截器可以有多个,所以执行拦截器链方法是一个递归调用的过程(在ReflectiveMethodInvocation中实现),它使用了一个变量currentInterceptorIndex记录了当前拦截器的下标:
- 判断currentInterceptorIndex是否与拦截器链的大小一致,如果一致说明已经走到了最后一个拦截器,拦截器走完就可以执行目标方法了,此时会通过反射执行目方法;
- 如果拦截器链未走完,会对currentInterceptorIndex加1,获取下一个拦截器,继续执行;
举个例子
比如定义了一个切面,里面设置了两个通知,分别是前置通知和环绕通知,要将这个切面作用到某个目标方法,在方法执行前后进行一些操作,Spring会将切面及通知封装为拦截器,在执行目标方法时,拦截器链中就会有两个拦截器,首先执行第一个拦截器,它是一个前置通知,执行完前置通知的方法后,会向后推进进入下一个拦截器:
执行到第二个拦截器,它是一个环绕通知,首先执行环绕通知中的前置操作(环绕通知中目标方法执行之前),运行完毕之后,该方法不会结束,会继续进入拦截器链的处理逻辑,等待目标方法执行之后再继续执行后置操作(环绕通知中目标方法执行之后的操作):
此时已经是拦截器链中最后一个,所以此时可以执行目标方法,执行完目标方法,拦截器链的逻辑已经执行完毕,所以对于第二个拦截器来说,会回到环绕通知中的处理逻辑,开始执行目标方法执行之后的后置操作。
总结
(1)在开启AOP的时候,它会向容器中注册一个AOP的代理对象创建器,它是一个后置处理器,在Spring容器中每个Bean实例化之后,初始化前后会进入到后置处理器对应的方法中,AOP创建代理对象并将原来的Bean替换就是在后置处理器的postProcessAfterInitialization方法中进行的。
(2)在创建AOP代理之前会先判断是否需要为当前Bean创建代理对象,因为并不是所有的Bean都需要进行创建,只有切面中设置要拦截的那些方法所在的Bean才需要创建AOP代理对象,所以一些Spring基础类、使用@Aspect标注的切面本身等Bean都会跳过。
(3)经过上述步骤后,会或获取所有的Advisor,Spring会将创建的切面包装成Advisor,所以可以理解为获取定义的所有切面,从中找出是否有匹配当前Bean的切面,如果有表示需要为Bean创建AOP代理,之后就会根据Bean的信息,比如是否实现了接口,来决定使用JDK动态代理还是Cglib创建代理对象,创建代理对象之后会替换掉原来的Bean,将这个代理对象返回。
(4)代理对象创建完毕之后,执行目标方法时,会进入到代理对象的业务逻辑中,在这里会获取匹配当前目标方法的所有Advisor(比如前置通知、后置通知等)将其转换成一个拦截器链,然后执行拦截器链,执行每个拦截器链的时候会执行对应通知中的方法,当拦截器链执行完毕之后,会通过反射执行真正的目标方法。
【Spring】AOP实现原理的更多相关文章
- 【Spring】Spring AOP实现原理
Spring AOP实现原理 在之前的一文中介绍过Spring AOP的功能使用,但是没有深究AOP的实现原理,今天正好看到几篇好文,于是就自己整理了一下AOP实现的几种方式,同时把代理模式相关知识也 ...
- Spring AOP 实现原理
什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...
- 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析
原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...
- Spring Aop底层原理详解
Spring Aop底层原理详解(来源于csdn:https://blog.csdn.net/baomw)
- spring AOP底层原理实现——jdk动态代理
spring AOP底层原理实现——jdk动态代理
- Spring AOP底层原理
------------------siwuxie095 Spring AOP 底层原理 AOP 即 Aspect Or ...
- jdk动态代理与cglib代理、spring Aop代理原理-代理使用浅析
原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...
- Spring框架系列(9) - Spring AOP实现原理详解之AOP切面的实现
前文,我们分析了Spring IOC的初始化过程和Bean的生命周期等,而Spring AOP也是基于IOC的Bean加载来实现的.本文主要介绍Spring AOP原理解析的切面实现过程(将切面类的所 ...
- Spring框架系列(10) - Spring AOP实现原理详解之AOP代理的创建
上文我们介绍了Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor).本文在此基 ...
- Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现
我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理.@pdai Spring框架系列 ...
随机推荐
- 快速实现 CDN 直播
功能简介 ZEGO Express SDK 支持推流到 CDN(Content Delivery Network,内容分发网络),包括转推 CDN 和直推 CDN 两种功能.开发者基于该功能可打通 R ...
- .NET Core中关于阿拉伯语环境下的坑:Input string was not in a correct format.
结论 .NET Core项目(.NET Framework没出现)在阿拉伯语(即语言名称是ar-开头的语言)环境下,将负数字符串转成数字,即int.Parse("-1")或Conv ...
- 模拟ArrayList(顺序表)的底层实现
模拟ArrayLIst的底层实现 package com.tedu.api04.list; import java.util.Objects; /** * @author LIGENSEN * Dat ...
- 磁盘 io 测试
磁盘 io 测试 参考链接 主机的磁盘io测试
- 借助 mkcert 和批处理命令生成局域网证书
借助 mkcert 和批处理命令生成局域网证书 自动获取ipv4,一键生成很方便 cd /d %~dp0 ipconfig |find "IPv4" > ipv4 set / ...
- 2.0 Python 数据结构与类型
数据类型是编程语言中的一个重要概念,它定义了数据的类型和提供了特定的操作和方法.在 python 中,数据类型的作用是将不同类型的数据进行分类和定义,例如数字.字符串.列表.元组.集合.字典等.这些数 ...
- 2023-08-12:用go语言写算法。实验室需要配制一种溶液,现在研究员面前有n种该物质的溶液, 每一种有无限多瓶,第i种的溶液体积为v[i],里面含有w[i]单位的该物质, 研究员每次可以选择一瓶
2023-08-12:用go语言写算法.实验室需要配制一种溶液,现在研究员面前有n种该物质的溶液, 每一种有无限多瓶,第i种的溶液体积为v[i],里面含有w[i]单位的该物质, 研究员每次可以选择一瓶 ...
- [信息安全] 加密算法:md5摘要算法 / sha256算法
1 MD5 1.1 算法定义 MD5的全称为 Message-Digest Algorithm,是一种被广泛使用的单向散列函数.属于Hash算法中一种比较重要算法--具有单项加密.加密结果唯一.安全性 ...
- aspnetcore 注册中心 consul
consul启动 . http://192.168.1.6:8500/ #以server方式启动,UI可以访问,boot引导自己选为leader,网段内PC可访问 consul agent -serv ...
- 【渗透测试】Vulnhub EMPIRE BREAKOUT
渗透环境 攻击机: IP: 192.168.149.128(Kali) 靶机: IP:192.168.149.130 靶机下载地址:https://www.vulnhub.com/entr ...