Spring AOP 之 我的孩子为什么不一样了
关于Spring的核心要素不可不论的就是IoC
和AOP
。而至于AOP
又向后衍生支撑了不少Spring的不少附属功能,包括Transcation
、Lazy Loading
、Caching
等等…Spring 问世许些年,也有不少大神大佬对流程有过详细解释,但是今儿,我也想用我的视角,给您细说说Spring AOP是怎样运转的。
前菜
在谈论AOP
的具体是实现之前我们还是得先稍微介绍一下Cglib
。Cglib
是一个代码生成库,底层基于ASM
进行实现,允许在程序编译阶段后进行类的创建,这也就是Spring实现AOP类代理的关键所在。
我们先简单看一下使用Cglib
对类进行代理的简单实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class CglibEnhancerDemo {
public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(Thread.currentThread().getContextClassLoader()); enhancer.setSuperclass(ProxyTargetClass.class);
enhancer.setCallback((MethodInterceptor)(o, method, objects, methodProxy) -> { System.out.println("Before method invoke"); Object result = methodProxy.invokeSuper(o, objects); System.out.printf("Complete invoke method, result is : %s\n", result); return result; }); ProxyTargetClass targetClass = (ProxyTargetClass)enhancer.create(); System.out.println(targetClass.saySomeThing());
} }
class ProxyTargetClass { private static final String name = "ShawJie"; public String saySomeThing() { return "Hello, " + name; } }
|
Spring 的 AOP实现
快速过了一下Cglib
对于类的增强过程,我们现在再来看看Spring是如何应用Cglib
对类进行增强的。为了保证整体内容统一,这边所有的逻辑分析皆基于Spring-Boot (v2.2.9.RELEASE) - somke-test-aop项目。Git地址: https://github.com/spring-projects/spring-boot.git。
Spring通过@Aspect
注解进行切面的声明,配合@Before
、@After
、@Around
、@Pointcut
等注解完成对类进行增强的整体逻辑。知其然亦知其所以然,其实不论是关于@Aspect
注解的扫描流程还是旁的,都脱离不开要把Spring
的Bean扫描装配流程走一遍,但该流程不是本文重点,因此不会在本篇中有过多分析(或许以后有时间会再一起来看一看),感兴趣的同学可也也可以自行先去了解一下。扫描注册核心方法: ClassPathBeanDefinitionScanner#doScan
,实例化核心方法:ConfigurableListableBeanFactory#preInstantiateSingletons
Advisor的扫描时机
在构想了上面两中切面的执行方式之后,不论是哪种方式,都需要扫描应用内所有的切面,并构建切面列表。Spring-Aop
也是这么做的,Spring
通过AutoProxyCreator
后处理器,在第一个单例Bean实例化之前对应用内的切面进行扫描构建。
哪来的AutoProxyCreator
在具体了解AutoProxyCreator
后处理器是如何扫描构建Advisor
列表之前,我们先看看这个AutoProxyCreator
后处理器是怎么来的。大家大抵都知道可以通过配置@EnableAspectJAutoProxy
注解以开启Aop功能,但是为什么SpringBoot
不需要加这个注解也能实现Aop的功能呢?这就涉及到之前文章所提到过得Spring自动装配了,有兴趣的可以点击这里去瞅瞅自动装配的逻辑和流程,这边就不再过多赘述,直接抓着核心说了。
Spring-Auto-Configure
包的spring.factories
文件内配置了需要自动装配检查的配置类,其中有org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
,在AopAutoConfiguration
类中我们可以看到自动装配的核心之一@Conditional
注解。
1 2 3 4 5
| @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true) public class AopAutoConfiguration { ... }
|
该注解表明在默认情况下会加载执行该配置类的配置内容,只有在配置了spring.aop.auto = false
时,Spring才不会装配Aop
相关的功能。
1 2 3 4 5 6 7
| @Configuration(proxyBeanMethods = false) @EnableAspectJAutoProxy(proxyTargetClass = true) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) static class CglibAutoProxyConfiguration {
}
|
在AopAutoConfiguration
类的配置下有一个CglibAutoProxyConfiguration
,同理,注解表明默认情况下(不进行任何配置)会去加载当前类的配置。在这个类上面我们看到了我们熟悉的@EnableAspectJAutoProxy
注解,这也就是我们在Spring-Boot
应用上不需要显示标明该注解的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { ... }
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } }
|
在@EnableAspectAutoProxy
注解内引入了AspectJAutoProxyRegistrar
注册器,在注册器的注册逻辑内我们可以看到通过调用registerAspectJAnnotationAutoProxyCreatorIfNecessary
向BeanFactory
内注册了AopProxyCreator
后处理器。
真真儿的扫描逻辑
在Spring的Bean实例化阶段,在实例化之前Spring会调用应用内所有BeanPostProcessor
的postProcessBeforeInstantiation
方法以在Bean被实例化之前进行一些处理。让我们看看AopProxyCreator
的实例化前的后置处理都做了什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) { Object cacheKey = getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) { if (this.advisedBeans.containsKey(cacheKey)) { return null; } if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return null; } }
TargetSource targetSource = getCustomTargetSource(beanClass, beanName); if (targetSource != null) { if (StringUtils.hasLength(beanName)) { this.targetSourcedBeans.add(beanName); } Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; }
return null; }
|
AopProxyCreator
的bean实例化前后置处理只看表面逻辑好像重点只是主要处理了CustomeTargetSource
的逻辑,但是在AopProxyCreator
的实现类AnnotationAwareAspectJAutoProxyCreator
中,shouldSkip
方法被重写并增加了了对beanFactory
内被标记了@Ascpet
进行扫描构建的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12
| @Override protected boolean shouldSkip(Class<?> beanClass, String beanName) { List<Advisor> candidateAdvisors = findCandidateAdvisors(); for (Advisor advisor : candidateAdvisors) { if (advisor instanceof AspectJPointcutAdvisor && ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) { return true; } } return super.shouldSkip(beanClass, beanName); }
|
1 2 3 4 5 6 7 8 9 10 11
| @Override protected List<Advisor> findCandidateAdvisors() { List<Advisor> advisors = super.findCandidateAdvisors(); if (this.aspectJAdvisorsBuilder != null) { advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); } return advisors; }
|
我们主要关注的是buildAspectJAdvisors
方法,由于代码较多就不在贴在这里了,我们在这里简单的理一下它的扫描流程,对具体逻辑感兴趣的同学可以自己打开IDE去瞅一瞅。
Double-Check-Lock
检查缓存的aspectBeanNames
是否为空,若为空则进行初始化流程- 从
BeanFactory
内捞出所有bean - 判断bean是否打上了
@Aspect
注解 - 向
aspectBeanNames
添加beanName
缓存,若切面类是单例,则直接作为缓存放入advisorsCache
,反之则作为advisorFactory
放入aspectFactoryCache
作为工厂缓存
增强类的构建
上一段中Advisor的扫描逻辑已经完成,我们系统内所以的切面类已经被Spring缓存起来了。在本节内容我们就要直接跳到Spring Bean的实例化环节,看看Spring AOP在Bean的实例化过程中,是如何对Bean做增强的。
在AbstractAutowireCapableBeanFactory#initializeBean
会进行Bean最后的实例化操作,在实例化时会调用应用内后处理器的postProcessAfterInitialization
方法对Bean进行实例化后处理逻辑。而AOP的BeanPostProcessor(AbstractAutoProxyCreator)
对该方法也进行了相关实现。
1 2 3 4 5 6 7 8 9 10 11 12
| @Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
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; }
this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
|
这段对bean进行检查并增强的逻辑里有俩重点
Spring是如何通过bean的元信息在我们之前扫描缓存下来的Advice列表里找到与之相关的Advice
Spring是如何对类进行增强的
谁是你的Advice
Spring在从缓存里捞出所有的Advice后,会将Advice列表和Bean元信息交由AopUtils#findAdvisorsThatCanApply
进行查找筛选并返回可匹配的Advice子集,其内部匹配逻辑由org.aspectj.weaver.patterns.Pointcut#match
负责,而具体逻辑则会交由PointCut
的子类进行实现,譬如处理@annotation(...)
的AnnotationPointcut
,还有处理execution(...)
的KindedPointcut
等等,具体逻辑由于过于冗长于此就不进行详细解释了,感兴趣的同学可以自行前往阅读。Ps:关于Pointcut表达式解析的逻辑可以参阅PointcutParser#resolvePointcutExpression
准备好了所以要装进去
在获取到匹配的Advice子集之后,最后的工作就是要把子集构建成Interceptor配合CGLIB完成对象增强的操作了。让我们直接跳转到CGLIB对于增强类的构建过程:CglibAopProxy#getProxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public Object getProxy(@Nullable ClassLoader classLoader) { try { Class<?> rootClass = this.advised.getTargetClass(); Class<?> proxySuperClass = rootClass; if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { proxySuperClass = rootClass.getSuperclass(); Class<?>[] additionalInterfaces = rootClass.getInterfaces(); for (Class<?> additionalInterface : additionalInterfaces) { this.advised.addInterface(additionalInterface); } } Enhancer enhancer = createEnhancer(); if (classLoader != null) { enhancer.setClassLoader(classLoader); if (classLoader instanceof SmartClassLoader && ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { enhancer.setUseCache(false); } } enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
return createProxyClassAndInstance(enhancer, callbacks); } catch (Exception e) { ... } }
|
在完成增强类的构建之后,BeanPostProcessor
会把beanFactory
里的bean替换为增强类的实现。至于Callback,AOP的实现有很多种,我们可以看一下SpringAop
的一个通用回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; Object target = null; TargetSource targetSource = this.advised.getTargetSource(); try { if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null); List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); Object retVal; if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = methodProxy.invoke(target, argsToUse); } else { retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); } retVal = processReturnType(proxy, target, method, retVal); return retVal; } finally { if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); } if (setProxyContext) { AopContext.setCurrentProxy(oldProxy); } } }
|
至此,看完增强类的实例化逻辑和Callback的调用逻辑相信大家已经对Spring AOP
的逻辑有了个比较线性的概念,甚至说我们可以仿照着Spring的逻辑通过BeanPostPorcessor
和CGLIB
自己也做一些简单的增强实现。AOP的开发方式使得我们可以以一个横向的视角去观察、审视应用的所有业务,也给我们在业务的通用化实现上有了更大的可执行空间。
尾巴
这篇内容写了很久…或许是内容的复杂度确实是有一些,亦或是因为怠惰又或许是什么旁的原因,总之写了俩月多终于还是赶在2021年来临之前写完了,挺丢人的。认认真真把AOP相关的源码从头到尾重新读了一遍,也发现了一些之前没有注意到的细节,之后还想配合着AOP把Spring的事务逻辑整理一下,就…2021年再说吧。因为是倚着源码读的,肯定也有些细节的地方没有注意,要是有说错写错的地方,也欢迎大家指正,蟹蟹(●ˇ∀ˇ●)。