关于Spring的核心要素不可不论的就是
IoC
和AOP
。而至于AOP
又向后衍生支撑了不少Spring的不少附属功能,包括Transcation
、Lazy Loading
、Caching
等等...Spring 问世许些年,也有不少大神大佬对流程有过详细解释,但是今儿,我也想用我的视角,给您细说说Spring AOP是怎样运转的。
前菜
在谈论AOP
的具体是实现之前我们还是得先稍微介绍一下Cglib
。Cglib
是一个代码生成库,底层基于ASM
进行实现,允许在程序编译阶段后进行类的创建,这也就是Spring实现AOP类代理的关键所在。
我们先简单看一下使用Cglib
对类进行代理的简单实现
public class CglibEnhancerDemo {
public static void main(String[] args) {
// 创建Enhancer进行增强类创建准备工作
Enhancer enhancer = new Enhancer();
// 为增强类指定ClassLoader
enhancer.setClassLoader(Thread.currentThread().getContextClassLoader());
// 设置需要代理的目标类(Cglib代理类是通过继承需要增强的目标类实现的)
enhancer.setSuperclass(ProxyTargetClass.class);
/*
* 设置方法调用时的回调,这里只是简单的进行了一些配置
* 具体使用场景可以根据方法签名进行额外逻辑处理
*/
enhancer.setCallback((MethodInterceptor)(o, method, objects, methodProxy) -> {
// do something before method invoke
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());
/*
* Before method invoke
* Complete invoke method, result is : Hello, ShawJie
* Hello, ShawJie
*/
}
}
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地址: github.com/spring-proj…。
Spring通过@Aspect
注解进行切面的声明,配合@Before
、@After
、@Around
、@Pointcut
等注解完成对类进行增强的整体逻辑。知其然亦知其所以然,其实不论是关于@Aspect
注解的扫描流程还是旁的,都脱离不开要把Spring
的Bean扫描装配流程走一遍,但该流程不是本文重点,因此不会在本篇中有过多分析(或许以后有时间会再一起来看一看),感兴趣的同学可也也可以自行先去了解一下。扫描注册核心方法: ClassPathBeanDefinitionScanner#doScan
,实例化核心方法:ConfigurableListableBeanFactory#preInstantiateSingletons
Advisor的扫描时机
-
在未曾了解
Spring Aop
之前,只知道类会被Cglib/JDK Proxy
进行代理,设想过切面的几种可能的执行方式:-
类在被代理后
Cglib
对方法附加通用实现,通用实现内动态获取对方法增强的逻辑并执行,这样实现的好处是可以在应用运行时动态的控制应用到方法的切面,对前面做增减操作。但缺点也很明显,每次方法执行之前都要解析切面的匹配规则并找出附和当前方法的切面列表,很大程度的降低了方法的执行效率。 -
类再被代理后,进行切面的匹配获取,并将获取到的切面列表通过
Cglib
动态构建成方法对应的Callback
列表,这样实现的好处就是弥补了动态获取切面列表降低方法执行效率的不足,但同时也不方便在运行时对方法的切面进行动态管理。
-
在构想了上面两中切面的执行方式之后,不论是哪种方式,都需要扫描应用内所有的切面,并构建切面列表。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
注解。
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
...
}
该注解表明在默认情况下会加载执行该配置类的配置内容,只有在配置了spring.aop.auto = false
时,Spring才不会装配Aop
相关的功能。
@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
应用上不需要显示标明该注解的原因。
@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
的实例化前的后置处理都做了什么。
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
// 构建bean的缓存名称
Object cacheKey = getCacheKey(beanClass, beanName);
// 判断是否是自订TargetSource所负责的Bean
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
// 判断当前Bean是否已经被处理过
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
// 判断是否是Aop基类(Advice/Pointcut/Advisor类或其子类)
// 若shouldSkip方法返回值为true, 则不应该被当前postProessor进行处理
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
/**
* 自订TargetSource是SpringAop的一个高级特性
* Aop代理的不是你的beanTraget而是targetSource
* 由于此特性不是本文重点 所以本文不会对相关内容进行过多解释
* 之后有机会可以再说说
*/
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
进行扫描构建的逻辑。
// AspectJAwareAdvisorAutoProxyCreator#shouldSkip
@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);
}
// AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors
@Override
protected List<Advisor> findCandidateAdvisors() {
// 在beanFactory中寻找Advisor的实现类列表
List<Advisor> advisors = super.findCandidateAdvisors();
if (this.aspectJAdvisorsBuilder != null) {
// 在beanFactory中寻找被标记了@Aspect注解的aspect bean
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)
对该方法也进行了相关实现。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 检查循环依赖逻辑
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 对bean进行检查 符合条件的bean会被进行增强操作
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 检查Bean是否由自订TargetSource负责
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
// 检查Bean是否已经检查过 且检查结果为不需要进行增强
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
// 判断是否是Aop基类(Advice/Pointcut/Advisor类或其子类)
// 若shouldSkip方法返回值为true, 则不应该被当前postProessor进行处理
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 查找当前Bean相关的advice 若有相关的advice 则用相关advice对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
// 撇去了一些非必要的代码 只留下了我们需要关心的部分
public Object getProxy(@Nullable ClassLoader classLoader) {
try {
Class<?> rootClass = this.advised.getTargetClass();
Class<?> proxySuperClass = rootClass;
// 检查需要被增强的目标类是否已经被增强过 若增强过则应该获取其父类作为CGLIB的父类
if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}
// 看到这儿是不是觉得有点熟悉 CGLIB的增强类创建逻辑它来了
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
// 基于CGLIB以子类作为增强类的模式 设置增强目标类为父类
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
// 将匹配的Advice构建成Cligb的Callback集合
Callback[] callbacks = getCallbacks(rootClass);
// 构建增强类
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (Exception e) {
...
}
}
在完成增强类的构建之后,BeanPostProcessor
会把beanFactory
里的bean替换为增强类的实现。至于Callback,AOP的实现有很多种,我们可以看一下SpringAop
的一个通用回调。
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);
// 获取Advice列表并构建为调用链
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) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
至此,看完增强类的实例化逻辑和Callback的调用逻辑相信大家已经对Spring AOP
的逻辑有了个比较线性的概念,甚至说我们可以仿照着Spring的逻辑通过BeanPostPorcessor
和CGLIB
自己也做一些简单的增强实现。AOP的开发方式使得我们可以以一个横向的视角去观察、审视应用的所有业务,也给我们在业务的通用化实现上有了更大的可执行空间。
尾巴
这篇内容写了很久...或许是内容的复杂度确实是有一些,亦或是因为怠惰又或许是什么旁的原因,总之写了俩月多终于还是赶在2021年来临之前写完了,挺丢人的。认认真真把AOP相关的源码从头到尾重新读了一遍,也发现了一些之前没有注意到的细节,之后还想配合着AOP把Spring的事务逻辑整理一下,就...2021年再说吧。因为是倚着源码读的,肯定也有些细节的地方没有注意,要是有说错写错的地方,也欢迎大家指正,蟹蟹(●ˇ∀ˇ●)。当然也欢迎来我的博客坐坐,看看别的Shawjie.cn。