• 对Spring AOP的进一步深入理解


    前言

    从AOP的开启,到切面生成,再到代理类注入,最后增强方法的调用整个流程做一个整理和理解。将Spring AOP功能整体串联起来。

    @EnableAspectJAutoProxy开启AOP功能

    前面已经研究过这个注解原理:Spring之@EnableAspectJAutoProxy开启AOP功能原理
    简单来说,就是这个注解通过@Import注解向Spring容器注入了一个BeanDefinition对象,这个BeanDefinition对象实例化后是AnnotationAwareAspectJAutoProxyCreator类,即AOP的入口类,这个类是一个BeanPostProcessor类。

    AOP入口类工作原理

    AnnotationAwareAspectJAutoProxyCreator是一个BeanPostProcessor类。那么在Spring容器bean实例化过程中,会调用postProcessBeforeInitialization方法和postProcessAfterInitialization方法。AOP入口是在postProcessAfterInitialization方法切入的,下面看这个方法源码:

    @Override
    	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    		if (bean != null) {
    			Object cacheKey = getCacheKey(bean.getClass(), beanName);
    			if (this.earlyProxyReferences.remove(cacheKey) != bean) {//这里的判断跟循环依赖有关,如果涉及到循环依赖,则在这里之前就生成了循环依赖对象的代理对象。这里不考虑这种情况,直接执行wrapIfNecessary方法
    				return wrapIfNecessary(bean, beanName, cacheKey);
    			}
    		}
    		return bean;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    下面看wrapIfNecessary方法源码:

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    		//           ......省略......
    		// Create proxy if we have advice.
    		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    		if (specificInterceptors != DO_NOT_PROXY) {
    			this.advisedBeans.put(cacheKey, Boolean.TRUE);
    			Object proxy = createProxy(
    					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    			this.proxyTypes.put(cacheKey, proxy.getClass());
    			return proxy;
    		}
    
    		//  ......省略......
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    截取重点代码,可以看到,首先收集正在实例化bean的Advisors(切面)和Adivces(增强),如果收集到了,则调用createProxy方法创建一个代理对象并返回。道理都懂,那么这个返回的代理对象,是如何与收集到的Advisors(切面)和Adivces(增强)交互的呢?这就需要查看createProxy方法源码:

    protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
    			@Nullable Object[] specificInterceptors, TargetSource targetSource) {
    
    		//  ...........省略..............
    
    		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    		proxyFactory.addAdvisors(advisors);
    		proxyFactory.setTargetSource(targetSource);
    		customizeProxyFactory(proxyFactory);
    
    		proxyFactory.setFrozen(this.freezeProxy);
    		if (advisorsPreFiltered()) {
    			proxyFactory.setPreFiltered(true);
    		}
    
    		// Use original ClassLoader if bean class not locally loaded in overriding class loader
    		ClassLoader classLoader = getProxyClassLoader();
    		if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
    			classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
    		}
    		return proxyFactory.getProxy(classLoader);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    可以看出,这里找出了bean对应的所有advisors对象,然后在生成代理对象定义增强时,执行增强代码。生成的代理对象返回给Spring容器中。

    调用代理对象方法

    调用代理对象的方法,最终走到的是ReflectiveMethodInvocation类的process()方法,该方法里维护了代理对象每个方法的一个拦截器链,拦截器链就是对原方法的增强。具体流程参考另一篇博客:Spring之Joinpoint类详解

    这里需要解释的一点就是,为何代理方法执行的时候会执行ReflectiveMethodInvocation的process方法呢?一定是在生成代理对象时,不管是jdk代理对象还是cglib代理对象,设置增强的回调函数时,设置成了ReflectiveMethodInvocation的process方法。拦截器链也是在设置回调函数时,根据获取到的advisors生成了拦截器链,只不过这部分代码,没有继续追踪源码,而是猜想而来的。Spring一定是按照这个逻辑,进行的AOP代理。

    综上所述,AOP的核心思想就是代理模式的应用。以BeanPostProcessor为切入,生成bean的代理对象。获取到代理对象的切面,并在代理的回调函数中执行切面中的增强方法。这样,在bean实例化完成后,调用其方法时,就走回调函数,然后根据拦截器链,执行增强方法。

    感悟

    上面讲到,通过 @EnableAspectJAutoProxy开启AOP功能的本质,就是这个注解@Import到Spring容器中一个BeanPostProcessor,这个BeanPostProcessor具有AOP功能。其他开启功能的注解思路和这个都一致。本质就是往Spring容器中添加bean,然后这些bean就具备了要开启的功能。
    只不过添加bean的方式略有不同。比如可以通过registry BeanDefinition方式加入,也可以通过FactoryBean方式加入,或者直接将BeanPostProcessor加入Spring容器等等。这些不同方式的加入,都是由开启功能的注解中@Import进来的类而决定的。

    @Async中AOP的应用

    我们自定义切面时,通过@Aspect来标注切面。那么第三方的注解,如@Async、@Transaction、@Cache等注解,是如何进行AOP操作的呢?不同注解实现方式略有不同,但是核心思想都是收集这些注解,然后生成注解所在类的切面。切点就是注解修饰的方法,而增强就是注解要增强的功能逻辑。以@Async为例进行说明:
    具体原理参考:一文彻底讲透@Async注解的原理和使用方法

    简单来说,就是通过@EnableAsync注解开启异步功能,这个注解将AsyncAnnotationBeanPostProcessor类加入了Spring容器,而这个类里,自己创建了Advisor,并设置了增强Advice。而切点PointCut就是注解修饰的方法。自己生成切面后,在AOP收集切面时,就会收集到这个切面,然后在实例化bean时,如果这个bean有切面,则生成代理对象,代理方法的增强就是@Async定义的异步增强。

    画外音

    之前一直认为一个对象,如果有接口,AOP生成代理对象时就用jdk动态代理,如果没有接口,则就用cglib代理。而在研究本篇博客内容时发现,在SpringBoot项目中,无论是否有接口,默认生成的代理对象都是cglib代理对象。查阅网上资料发现是在SpringBoot2.x版本,默认都使用了cglib代理来规避jdk动态代理面向接口代理而产生的一些问题。详情自行百度即可,这里只是记录一下这个情况。

  • 相关阅读:
    公共建筑节能大数据应用进展
    板带纠偏控制系统伺服比例阀放大器
    PHP:Shuttle Sort穿梭排序(附完整源码)
    机器人开发--减速机
    Python数据结构与算法分析第2版 - 第2章习题
    连锁门店如何利用共享门店模式实现转型升级
    px、em、rem、百分比的区别
    C语言题收录(五)
    git简明指南
    DiffuSEEG:一种基于stable diffusion 的SEEG数据补全方法
  • 原文地址:https://blog.csdn.net/qq1309664161/article/details/126936726