对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;
	}

下面看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;
		}

		//  ......省略......
	}

截取重点代码,可以看到,首先收集正在实例化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);
	}

可以看出,这里找出了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动态代理面向接口代理而产生的一些问题。详情自行百度即可,这里只是记录一下这个情况。

猜你喜欢

转载自blog.csdn.net/qq1309664161/article/details/126936726
今日推荐