Skip to main content

SpringAOP有哪些核心组件?

作者:程序员马丁

在线博客:https://open8gu.com

note

大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个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

image.png

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 后,它既是一个记录了调用方法和调用参数等信息的上下文,又是拦截器链本身。

image.png

当我们的拦截器执行完逻辑以后,当调用 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 也提供了手动代理途径,那就是 ProxyFactoryBeanAspectJProxyFactory,它们的原理和自动代理一样,点进去源码,我们依然可以看到诸如 AspectJAdvisorFactoryAopProxyFactory 等熟悉的组件。

关于 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());
}

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 的懒加载和延迟注入是什么?两者有何区别?