SpringAOP有哪些核心组件?
作者:程序员马丁
在线博客:https://open8gu.com
大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。
问题详解
1. 通知器 Advisor
在 SpringAOP 中,一切基于 AOP 实现的增强逻辑都以一个 Advisor
作为基本单位。这个顶级接口从字面上直译为通知器。
在通常情况下,我们只会接触到它的两个子类:
- 切点通知器
PointcutAdvisor
:它由通知Advice
和切点Pointcut
两部分组件,它是最常用的通知器。 - 引介通知器
IntroductionInfo
:用于让被代理可以实现原本没实现的接口,它一般很少用到。
它们的接口定义如下:
public interface Advisor {
// 获取通知操作,即具体的操作逻辑
Advice getAdvice();
// 每个切入目标是否都需要一个新的通知器
boolean isPerInstance();
}
public interface PointcutAdvisor extends Advisor {
// 获取切点,即类或方法匹配器
Pointcut getPointcut();
}
public interface IntroductionInfo {
// 需要引入的接口,即被切入的类等于变相的实现了这些接口
Class<?>[] getInterfaces();
}
简单的来说,不管是通过 AspectJ 的切面也好,通过 Spring 的 Advice
也好,我们无论选择哪种 AOP 方式,最终它们都会被解析并封装为一个个 Advisor
,然后最终以这种方式应用于代理对象中。
2. 切点 Pointcut
PointcutAdvisor
是我们最经常打交道的通知器,在 Spring 中,大部分的 AOP 增强其实都是通过它实现的。相比起普通的 Advisor
,它多了切点 Pointcut
这么个组件,它用于判断某个类中的某个方法是否可以被增强。
我们可以简单的看看它的接口定义:
public interface Pointcut {
// 用于检查类是否匹配,等同于一个 Predicate<Class<?>>
ClassFilter getClassFilter();
// 用于检查方法是否匹配,等同于一个 Predicate<Method>
// 注意:
// 一个类需要先保证通过 ClassFilter 的匹配,然后才会由 MethodMatcher 匹配
MethodMatcher getMethodMatcher();
}
Pointcut
的实现类比较多,但是最常见的几种是:
AspectJExpressionPointcut
:在我们声明的 AspectJ 切面类中,那些带有@Pointcut
注解的方法最终就会被解析为该类型的切点。TransactionAttributeSourcePointcut
:这个切点用于匹配带有声明式事务的方法(@Transcational
)。CacheOperationSourcePointcut
:这个切点用于匹配带有 SpringCache 声明式缓存的方法(@Cacheable
等)。AnnotationMatchingPointcut
:基于注解的切点,除了标准的 JDK 注解外,它也支持基于 Spring 的组合注解来拦截特定的元注解。
- 关于 AspecJ 与 SpringAOP 的关系,请参见:SpingAOP 和 AspectJ 有何关系?
- 关于 Spring 的组合注解机制,请参见:✅ Spring 的组合注解是什么?
- 需要澄清一下,关于
@Transcational
与@Cacheable
等注解的拦截,其实并不是直接让切点去检查方法上是否有注解,而是先通过对应的 Parser 解析方法对应的元数据,然后再通过元数据确认是否需要被拦截,这里便于理解。
3. 通知 Advice
每一个 Advisor
都绝对包含一个通知 Advice
,它表示的是我们真正需要织入代理对象中的增强逻辑。简单的来说,如果 Pointcut
表示的是“在哪里?”,那么 Advice
表示的就是 “要做什么?”。
Advice
本身是一个标示性接口,它有很多的子接口,但是按切入位置可以简单分为三类:
- 执行前通知
BeforeAdvice
,即方法执行前调用。 - 环绕通知
InterceptorAdvice
:即直接拦截方法调用。 - 后置通知
AfterAdvice
:即方法执行后调用,这里又分返回后通知AfterReturningAdvice
和异常通知ThrowsAdvice
两种。
你到这里一定会觉得跟 AspectJ 的切面有种莫名的既视感,没错,AspectJ 切面中的切面方法最终也会被解析成这几种 Advice
。
4. 方法拦截器 MethodInterceptor
Advice 有非常多的子类,而在这些子类里面,又以方法拦截器 MethodInterceptor
使用的最为广泛。
与我们日常使用 AspectJ 不同,Spring 自带的 AOP 组件几乎百分之九十都是基于方法拦截器 MethodInterceptor
实现的。
// MethodInterceptor -> Interceptor -> Advice
public interface MethodInterceptor extends Interceptor {
// 拦截方法调用
Object invoke(MethodInvocation invocation) throws Throwable;
}
这里随便举几个例子:
TransactionInterceptor
:事务方法拦截器,用于实现声明式事务(@Transcational
)。AnnotationAsyncExecutionInterceptor
:异步方法拦截器,实现异步调用(@Async
)。AspectJXXXAdvice
:AspectJ 切面方法拦截器(@Before
、@After
、@Around
等等)。
其中,相比起其他的 Advice
,方法拦截器 MethodInterceptor
天然的就是一种环绕增强,即切面逻辑里面可以完成的感知并干涉方法调用的 完整 流程,比如拦截方法调用,修改调用参数,拦截方法的返回值,或者拦截方法抛出的异常等等……
5. 织入点 Joinpoint
方法拦截器强大功能的源头,其实在于它接受的 MethodInvocation
参数。
MethodInvocation
本质上是一个 Joinpoint,它表示是“一次被拦截的方法调用”,它里面包含:
- 被代理对象
Target
(通过getThis
获取)。 - 被拦截的方法对象
Method
。 - 本次方法的调用参数
arguments
。 - 所有可以拦截这次方法调用的方法拦截器(拦截器链本身)。
这里我们可以结合一个典型的方法拦截器的实现感受一下:
public class MethodResultAutoOperateAdvisor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// 获取方法上的特定注解,然后做一些处理
Method method = methodInvocation.getMethod();
AutoOperate annotation = AnnotatedElementUtils.findMergedAnnotation(method, AutoOperate.class);
doSomethingForAnnotatedMethod(
annotation, method, result, methodInvocation.getArguments()
);
Object result = null;
try {
// 递归执行下一个拦截器
Object result = methodInvocation.proceed();
} catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
return result;
}
}
简单的来说,当我们拿到 MethodInvocation
后,它既是一个记录了调用方法和调用参数等信息的上下文,又是拦截器链本身。
当我们的拦截器执行完逻辑以后,当调用 MethodInvocation.processed()
方法时,将会递归的调用链上的下一个拦截器,直至递归到原始方法并完成调用后拿到返回值。这个过程与 Gateway 或者 Tomcat 的过滤器链机制非常像。
这里顺带提一下,有意思的是,关于 AOP 这一整套 API 规范实际上其实也不是 Spring 定义的,它们是由 AOP 联盟 aopalliance 这个第三方机构提出的。很多框架的 AOP 其实都实现了这套规范,比如 Google 的 Guice。
具体可以参见:AOP Alliance sourl.cn/3dZyNb
6. AbstractAutoProxyCreator 自动代理处理器
Spring 基于后处理器 BeanPostProcessor
实现了自动生成代理的功能。
简单的来说,就是如果你在对象或者方法上加了某些特定的注解,那么 Spring 在初始化 Bean 的时候,就会自动的为你的 Bean 创建一个代理对象,此后容器中获取这个 Bean 的时候,都会获取到代理对象。
用于进行自动代理的后处理器体系中的顶层抽象类是 AbstractAutoProxyCreator
,我们重点关注它的三个常用子类:
- AbstractAdvisorAutoProxyCreator:抽象类,继承了
AbstractAutoProxyCreator
,用于定义基于Advisor
进行代理的基本逻辑。 - AspectJAwareAdvisorAutoProxyCreator :实现类,继承了
AbstractAdvisorAutoProxyCreator
,在前者的基础上,用于让那些基于 AspectJ 的Advisor
能够按指定的规则排序。 - AnnotationAwareAspectJAutoProxyCreator:实现类,继承了
AspectJAwareAdvisorAutoProxyCreator
。进一步支持基于 AspectJ 注解(如@Aspect
、@Pointcut
、@Before
、@After
等)的切面,简而言之,就是支持将这些注解方法也解析成Advisor
。
这一层一层的继承下来,最终的 AnnotationAwareAspectJAutoProxyCreator
就是我们项目中默认使用的后处理器了。
关于后处理器与处理器的调用时机,请参见:✅ Bean 的生命周期?
7. ProxyFactoryBean 手动代理工厂
除了这种自动代理的方式,Spring 也提供了手动代理途径,那就是 ProxyFactoryBean
或 AspectJProxyFactory
,它们的原理和自动代理一样,点进去源码,我们依然可以看到诸如 AspectJAdvisorFactory
、AopProxyFactory
等熟悉的组件。
关于
FactoryBean
,可以参见:✅ 什么是 FactoryBean?
8. 代理工厂 ProxyFactory
代理工厂 ProxyFactory
是整个 SpringAOP 体系中最底层的一环,所有的 Spring 代理对象都是通过它创建出来的。
当 Spring 要创建一个代理对象时,它会先创建一个 ProxyFactory
,然后获取 Advisor
等相关的组件和配置,然后将其塞到 ProxyFactory
,最后再调用 getProxy
方法根据配置创建一个代理对象。
简单的来说,ProxyFactory
相当于一个加工厂,而 Advisor
等其他的组件和配置相当于原料,你只管往里头塞,最后 ProxyFactory
帮你理顺了最后组装出一个代理对象。
我们可以从这段 AbstractAutoProxyCreator
创建代理对象的代码中简单感受一下它的用法:
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
// 让 Bean 保留自己的类型
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
// 基于当前的代理配置(及 AbstractAutoProxyCreator 的配置)创建一个代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
// 是否需要代理目标类?(即 CGLib 代理)
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
// 代理目标类的所有接口(即 JDK 代理)
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 搜集 Spring 容器中所有的 Advisor,如果有单独的 Advice 则强行适配为 Advisor
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
// 添加要应用的 Advisor
proxyFactory.addAdvisors(advisors);
// 设置 TargetSource (其实就是用于获取被代理对象的组件,如果是延迟注入那么这一步就会用上)
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 创建代理对象
return proxyFactory.getProxy(getProxyClassLoader());
}
- 关于代码中提到的 JDK 代理和 CGLib 代理,请参见:✅ JDK 代理和 CGLib 代理的区别?
- Spring 对
@Configuration
注解的配置类,在 Full 模式下会直接使用ProxyFactory
创建对应的代理对象,关于这个例子,请参见:✅ @Configuration 和@Component 有什么区别?
9. 目标源 TargetSource
我们知道,SpringAOP 的核心是代理,而既然是代理,那就得有被代理的东西,也就是“target”,而 TargetSource 是用于获取被代理对象的组件。
当 Spring 通过 ProxyFactory
真正的创建了一个代理对象后,每次当调用代理对象的方法时,它们最终都会被调用拦截器 InvocationHandler
—— 不管是 JDK 代理的拦截器,还是 CGLib 代理的拦截器 —— 拦截。
在方法调用开始前,Spring 将会通过 TargetSource
实时的获取一个被代理对象,然后将其与所有的方法拦截器一并封装为 MethodInvocation
(也就是上文提到的 Joinpoint
)。
我们看一下 JdkDynamicAopProxy
源码感受一下:
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ... ...
// 获取被代理对象
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 获取方法拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 创建 MethodInvocation,这里面包含了上文的代理对象和拦截器链
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 触发调用
retVal = invocation.proceed();
}
// ... ...
}
}
这个 TargetSource
虽然平时基本接触不到,但是实际上它是 Spring 中很多神奇机制的实现基础,比如我们 前面提到的 @Autowired
配合 @Lazy
实现延迟注入,把注入的 Bean 延迟到真正调用里面的方法时才加载,实际上就是基于 LazyInitTargetSource
这个 TargetSource
实现的。
关于
@Lazy
注解和延迟注入,请参见:✅ Spring 的懒加载和延迟注入是什么?两者有何区别?