【重写SpringFramework】第二章aop模块:单通知切面Advisor(chapter 2-3)
1. 前言
Spring AOP 关注的是方法拦截,扩展了 Advice
接口,实现了对方法的增强。理论上,Advice
可以对所有的方法进行增强,但这显然不是我们所期望的。AOP 理念的核心是需求驱动服务,只对有需要的方法进行增强,而拦截所有方法只是一种特殊情况。鉴于此,我们需要一种灵活的方式来动态地标定适用范围。
从几何学的视角来看,如果把单个方法视为一维的点,那么将若干方法连缀起来就构成了二维的面,这个面就是通常所说的切面。Spring 提供了两种构建切面的方式,一是切点(pointcut),二是引入(introduction)。此外,Spring 为了将增强逻辑与切面特性整合在一起,提供了 Advisor
接口。
2. 切点
2.1 概述
Pointcut
由 MethodMatcher
与 ClassFilter
组成,也就是说切点是对指定的类和方法进行匹配的。ClassFilter
和 MethodMatcher
两个组件的配合是比较灵活的,可以让其中一个起作用,也可以让两者都起作用。注意,这里起作用的意思是指有效拦截(体现切面的特性) 。因为 ClassFilter
和 MethodMatcher
都有一个特殊实例,默认拦截所有的类或方法。
2.2 MethodMatcher
MethodMatcher
接口的作用是对方法进行匹配,按照匹配逻辑可以分为两种情况。一是静态匹配,对方法属性进行匹配,主要是注解。二是动态匹配,除了匹配方法属性,还要匹配运行时传入的参数。isRuntime
方法表示是否支持动态匹配,两个参数的 matches
方法表示静态匹配,三个参数的 matches
方法表示动态匹配。
public interface MethodMatcher {
//静态匹配
boolean matches(Method method, Class<?> targetClass);
//动态匹配
boolean matches(Method method, Class<?> targetClass, Object... args);
//是否支持动态匹配
boolean isRuntime();
}
StaticMethodMatcher
没有直接实现静态匹配,而是剥离了与静态匹配无关的方法。静态匹配不关心运行时的参数,三个参数的 matches
方法是无效的,直接抛出异常。子类只需要实现两个参数的 matches
方法,完成静态匹配即可。
public abstract class StaticMethodMatcher implements MethodMatcher {
@Override
public final boolean matches(Method method, Class<?> targetClass, Object... args) {
throw new UnsupportedOperationException();
}
@Override
public final boolean isRuntime() {
return false;
}
}
注:Spring AOP 使用静态匹配,而 AspectJ 则通过动态匹配的方式来构建切面。本教程仅实现 Spring AOP,因此主要关注静态匹配的实现。
2.3 ClassFilter
ClassFilter
接口的作用是过滤符合条件的接口或类,静态字段 TRUE
是一个特殊实例,默认匹配所有的类。类过滤器是粗粒度的匹配方式,较少使用,仅了解即可。
public interface ClassFilter {
ClassFilter TRUE = clazz -> true;
boolean matches(Class<?> clazz);
}
2.4 Pointcut
Pointcut
接口的定义了两个方法,说明切点就是类过滤器和方法匹配器的组合。
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
StaticMethodMatcherPointcut
是 Pointcut
接口的抽象子类,从类名可以看出,侧重点是静态的方法匹配。从代码中也可以看出端倪,classFilter
字段使用的是默认的 ClassFilter
,说明并不关心类的过滤。子类只需要实现两个参数的 matches
方法,完成静态方法匹配即可。一般情况下,我们只需要继承该类,实现静态方法的匹配逻辑即可。举个例子,数据库事务注解 @Transactional
就是通过子类 TransactionAttributeSourcePointcut
进行匹配的,我们将在第四章 tx 模块进行介绍。
public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut {
private ClassFilter classFilter = ClassFilter.TRUE;
@Override
public ClassFilter getClassFilter() {
return classFilter;
}
@Override
public MethodMatcher getMethodMatcher() {
return this;
}
}
2.5 简单实现
本节我们将使用 AnnotationMatchingPointcut
实现一个简单的切面,判断逻辑就是检查类或方法是否声明了指定注解。从类图结构中可以看到,AnnotationMatchingPointcut
是一个典型的切点实现类,由一个类过滤器和一个方法匹配器组成。
AnnotationMatchingPointcut
实现了 Pointcut
接口,作用是检查类或方法上是否声明了指定的注解。从构造方法可以看到,具体的匹配逻辑是由 AnnotationClassFilter
和 AnnotationMethodMatcher
两个组件完成的。
public class AnnotationMatchingPointcut implements Pointcut {
private final ClassFilter classFilter = ClassFilter.TRUE;
private final MethodMatcher methodMatcher = MethodMatcher.TRUE;
public AnnotationMatchingPointcut(Class<? extends Annotation> classAnnotationType,
Class<? extends Annotation> methodAnnotationType) {
if (classAnnotationType != null) {
this.classFilter = new AnnotationClassFilter(classAnnotationType);
}
if (methodAnnotationType != null) {
this.methodMatcher = new AnnotationMethodMatcher(methodAnnotationType);
}
}
}
AnnotationClassFilter
实现了 ClassFilter
接口,如果目标类及其父类声明了指定的注解,则认为匹配成功。
public class AnnotationClassFilter implements ClassFilter {
private final Class<? extends Annotation> annotationType;
public AnnotationClassFilter(Class<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}
@Override
public boolean matches(Class<?> clazz) {
//当前类及其父类是否存在指定注解
return AnnotationUtils.findAnnotation(clazz, this.annotationType) != null;
}
}
AnnotationMethodMatcher
实现了 MethodMatcher
接口,静态方法匹配的逻辑是根据方法上的注解来判断。matches
方法先检查当前类的方法上是否声明指定注解,如果不存在,则尝试从当前类的所有接口中寻找合适的方法进行匹配。只要满足任何一条件,则认为当前方法是匹配的。
public class AnnotationMethodMatcher extends StaticMethodMatcher {
private final Class<? extends Annotation> annotationType;
public AnnotationMethodMatcher(Class<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
//当前类的方法上是否存在指定注解
if (method.isAnnotationPresent(this.annotationType)) {
return true;
}
//接口的方法上是否存在指定注解
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
return (specificMethod != method && specificMethod.isAnnotationPresent(this.annotationType));
}
}
3. Advisor
3.1 概述
Advisor
接口是一个非常重要的组件,我们先来看官方的定义。
Base interface holding AOP advice (action to take at a joinpoint) and a filter determining the applicability of the advice (such as a pointcut). This interface is not for use by Spring users, but to allow for commonality in support for different types of advice.
Advisor
接口持有一个Advice
,以及用于厘定Advice
适用范围的过滤器(比如切点)。该接口并不是面向开发者的,而是为了不同类型的通知而提供的通用性。
这两句话包含了丰富的信息,直指三大关键点:拦截、切面以及适配。其中拦截和切面是组成 AOP 的基石,适配则是为不同类型的通知提供统一的抽象。
- 持有一个
Advice
实例,说明Advisor
是单通知的。 - 持有一个过滤器,用于决定哪些方法会被拦截。单个方法是一个点,若干方法就构成了一个面,也就是切面(aspect)。
Advisor
作为统一的抽象,来整合Advice
、MethodInterceptor
、Advisor
等不同类型的通知,这既是所谓的通用性。
3.2 继承结构
Advisor
的继承结构分为两个部分,划分依据是构建切面的方式,也就是引入和切点。其中,IntroductionAdvisor
接口的作用是通过引入的方式进行拦截,仅了解即可。我们关心的是以 Pointcut
方式构成的切面,因此着重来看 PointcutAdvisor
是如何实现的。
Advisor
作为顶级接口,定义了获取 Advice
的方法,也就是增强逻辑。至于如何构建切面,则由子类完成。
public interface Advisor {
Advice getAdvice();
}
PointcutAdvisor
继承了 Advisor
接口,通过 Pointcut
提供切面特性。
public interface PointcutAdvisor extends Advisor{
Pointcut getPointcut();
}
AbstractPointcutAdvisor
持有一个 Advice
实例,拥有了拦截功能,具体的切点则由子类提供。
public abstract class AbstractPointcutAdvisor implements PointcutAdvisor{
private Advice advice;
}
3.3 DefaultPointcutAdvisor
DefaultPointcutAdvisor
有两个构造方法,如果不指定 pointcut
入参,那么默认的切点将会对任意类和方法进行拦截。在这种情况下,DefaultPointcutAdvisor
退化成了 Advice
,只拥有拦截的功能,失去了切面的特性。这样做的意义何在?事实上 DefaultPointcutAdvisor
作为兜底的存在,目的是对 Advice
和 MethodInterceptor
进行适配。
public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor{
private Pointcut pointcut = Pointcut.TRUE;
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
}
4. 适配器
4.1 概述
Advisor
除了增强和切面这两个基本功能外,还可以作为统一的抽象,来整合 Advice
、Advisor
、Interceptor
等不同类型的通知。如下图所示,整个继承结构可以分为两个部分,一是以 AdvisorAdapter
接口为代表的适配器对象,二是通过 AdvisorAdapterRegistry
接口注册适配器,并提供转换的功能。
4.2 AdvisorAdapter
AdvisorAdapter
接口的作用是将各种 Advice
实例包装成适配器对象,getInterceptor
方法是核心适配方法,作用是获取 Advisor
的拦截器对象。这说明适配的目的是为了获得拦截器对象,因此只需要对三种通知进行适配,MethodInterceptor
本身就是拦截器,也就不需要进行包装了。
public interface AdvisorAdapter {
boolean supportsAdvice(Advice advice);
MethodInterceptor getInterceptor(Advisor advisor);
}
AdvisorAdapter
接口有三个实现类,分别对应前置通知、返回通知和异常通知。这些实现类大同小异,以 MethodBeforeAdviceAdapter
为例,getInterceptor
方法是将前置通知包装成了 MethodBeforeAdviceInterceptor
。
public class MethodBeforeAdviceAdapter implements AdvisorAdapter {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
4.3 AdvisorAdapterRegistry
AdvisorAdapterRegistry
接口的作用是注册适配器,以及如何进行适配。其中注册工作是由 registerAdvisorAdapter
方法完成的,适配工作分为两步,首先由 wrap
方法将一个通知包装成 Advisor
对象,然后调用 getInterceptors
方法的从 Advisor
对象中获取拦截器的集合。
wrap
方法的作用是将任意类型的通知包装成一个Advisor
实例registerAdvisorAdapter
方法的作用是注册适配器getInterceptors
方法的作用是获取Advisor
对象的拦截器集合
public interface AdvisorAdapterRegistry {
Advisor wrap(Object advice);
MethodInterceptor[] getInterceptors(Advisor advisor);
void registerAdvisorAdapter(AdvisorAdapter adapter);
}
DefaultAdvisorAdapterRegistry
是默认的实现类,持有一个适配器集合,构造函数中注册了三个默认的适配器。wrap
方法将 MethodInterceptor
和 Advice
都包装成了 DefaultPointcutAdvisor
对象,如果是 Advisor
的子类,直接强转即可。从这里可以看到 DefaultPointcutAdvisor
的主要作用是适配,而不是切面。
适配只是手段,最终目的是为了拿到拦截器的集合。在 getInterceptors
方法中,首先获取 Advisor
对象持有的 Advice
对象,由于 Advice
可能是某个具体的通知,也可能是 MethodInterceptor
的实现类,因此需要分情况处理,然后拿到拦截器的集合。
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry {
private final List<AdvisorAdapter> adapters = new ArrayList<>(3);
public DefaultAdvisorAdapterRegistry() {
registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
registerAdvisorAdapter(new AfterReturningAdviceAdapter());
registerAdvisorAdapter(new ThrowsAdviceAdapter());
}
@Override
public Advisor wrap(Object obj) {
//如果是Advisor类型,直接强转
if(obj instanceof Advisor){
return (Advisor) obj;
}
//对MethodInterceptor进行包装
Advice advice = (Advice) obj;
if (advice instanceof MethodInterceptor) {
return new DefaultPointcutAdvisor(advice);
}
//对Advice进行包装
for (AdvisorAdapter adapter : this.adapters) {
if (adapter.supportsAdvice(advice)) {
return new DefaultPointcutAdvisor(advice);
}
}
throw new RuntimeException("未知的Advice类型");
}
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) {
List<MethodInterceptor> interceptors = new ArrayList<>(3);
Advice advice = advisor.getAdvice();
//检查Advice是否为MethodInterceptor
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
//寻找适配Advice的MethodInterceptor
for (AdvisorAdapter adapter : this.adapters) {
if (adapter.supportsAdvice(advice)) {
interceptors.add(adapter.getInterceptor(advisor));
}
}
return interceptors.toArray(new MethodInterceptor[interceptors.size()]);
}
}
5. 测试
5.1 切点
首先准备一个测试类 AdvisorTarget
,定义两个方法,其中一个方法声明注解。这里不关心注解的实际意义,仅起到标识作用。
//测试类
public class AdvisorTarget {
@Autowired
public void handle() {}
public void handle2() {}
}
在测试方法中,首先创建 AnnotationMethodMatcher
实例,指定匹配方法的逻辑为声明了 @Autowired
注解。然后创建 AnnotationMatchingPointcut
对象,由于没有指定 classFilter
属性,说明默认所有类都符合条件。最后遍历 AdapterTarget
的所有方法,将符合 MethodMatcher
匹配逻辑的方法名打印出来。
//测试方法
@Test
public void testPointcut() {
AnnotationMethodMatcher methodMatcher = new AnnotationMethodMatcher(Autowired.class);
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(methodMatcher);
for (Method method : AdapterTarget.class.getDeclaredMethods()) {
boolean matches = pointcut.getMethodMatcher().matches(method, AdapterTarget.class);
if (matches) {
System.out.println("匹配方法:" + method.getName());
}
}
}
从测试结果可以看到,声明了 @Autowired
注解的方法名被打印出来,说明切面确实生效了。
匹配方法:handle
5.2 适配器
先定义一个辅助类 AdvisorAdapterSupport
,其中 registry
字段表示注册器,advisors
字段表示 Advisor
集合。addAdvice
方法可以添加多种类型的增强对象,首先通过 AdapterRegistry
适配包装成一个 Advisor
对象,然后添加到集合中。将该类与上一节的辅助类 AdviceSupport
进行对比可以发现,AdvisorAdapterSupport
使用适配器模式代替了 if 条件语句。
//测试类
public class AdvisorAdapterSupport {
private Object target;
private AdvisorAdapterRegistry registry = new DefaultAdvisorAdapterRegistry();
private List<Advisor> advisors = new LinkedList<>();
public void addAdvice(Object advice){
Advisor advisor = this.registry.wrap(advice);
this.advisors.add(advisor);
}
}
在测试方法中,首先创建 AdvisorAdapterSupport
对象,然后分别注册了一个 MethodInterceptor
、MethodBeforeAdvice
和 AfterReturningAdvice
接口的实现类,它们最终都被统一适配成 Advisor
实例。
//测试方法
@Test
public void testAdvisorAdapter() throws Throwable {
AdvisorAdapterSupport support = new AdvisorAdapterSupport();
support.addAdvice(new SimpleMethodInterceptor());
support.addAdvice(new SimpleAfterReturningAdvice());
support.addAdvice(new SimpleMethodBeforeAdvice());
support.setTarget(new TargetObj());
support.invoke("handle");
}
从测试结果来看,拦截器和通知可以同时生效,AdvisorAdapterRegistry
的适配功能发挥了作用。
前置拦截,方法名:handle
前置通知,方法名:handle
执行目标方法
返回通知,方法名:handle
返回拦截,方法名:handle
6. 总结
本节讨论了如何将单个方法的增强扩展到若干方法的增强,将一个点连缀成一个面,这一过程需要遵循一定的规则。Spring 提供了两种构建切面的方式,一是切点,二是引入。为了简化代码,我们只关心切点,引入仅了解即可。切点实际上是一个组合体,由 ClassFilter
和 MethodMatcher
构成,前者对类进行过滤,后者对方法进行匹配。大多数情况下,我们只关心方法的匹配。方法匹配又分为静态匹配和动态匹配,所谓静态匹配是指方法签名,包括方法上的注解之类,这些信息在编译期就能确定。所谓动态匹配是指除了静态匹配的内容,还要匹配运行时传入方法的参数。经过不断地瘦身,构建切面的主要途径是对方法进行静态匹配。
本节还介绍了另一个重要的组件 Advisor
,该组件有三个作用。其一,将 Advice
与切点/引入组合在一起,拥有了拦截语义和切面特性,这是构成 AOP 的基础。其二,持有一个 Advice
实例,说明一个 Advisor
只能实现一种增强逻辑。一个应用中可能需要多个 AOP 功能,则需要定义多个 Advisor
。其三,通知对象分为三种,Advice
、MethodInterceptor
和 Advisor
,需要进行统一的适配。
7. 项目信息
新增修改一览,新增(22),修改(0)
aop
└─ src
├─ main
│ └─ java
│ └─ cn.stimd.springwheel.aop
│ ├─ framework
│ │ ├─ adapter
│ │ │ ├─ AdvisorAdapter.java (+)
│ │ │ ├─ AdvisorAdapterRegistry.java (+)
│ │ │ ├─ AfterReturningAdviceAdapter.java (+)
│ │ │ ├─ DefaultAdvisorAdapterRegistry.java (+)
│ │ │ ├─ MethodBeforeAdviceAdapter.java (+)
│ │ │ └─ ThrowsAdviceAdapter.java (+)
│ ├─ support
│ │ ├─ annotation
│ │ │ ├─ AnnotationClassFilter.java (+)
│ │ │ ├─ AnnotationMatchingPointcut.java (+)
│ │ │ └─ AnnotationMethodMatcher.java (+)
│ │ ├─ AbstractGenericPointcutAdvisor.java (+)
│ │ ├─ AbstractPointcutAdvisor.java (+)
│ │ ├─ DefaultPointcutAdvisor.java (+)
│ │ ├─ StaticMethodMatcher.java (+)
│ │ └─ StaticMethodMatcherPointcut.java (+)
│ ├─ Advisor.java (+)
│ ├─ ClassFilter.java (+)
│ ├─ MethodMatcher.java (+)
│ ├─ Pointcut.java (+)
│ └─ PointcutAdvisor.java (+)
└─ test
└─ java
└─ cn.stimd.springwheel.aop.test
└─ advisor
├─ AdvisorTarget.java (+)
├─ AdvisorAdapterSupport.java (+)
└─ AdvisorTest.java (+)
注:+号表示新增、*表示修改
- 项目地址:gitee.com/stimd/sprin…
- 本节分支:gitee.com/stimd/sprin…
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
欢迎关注公众号【Java编程探微】,加群一起讨论。
原创不易,觉得内容不错请关注、点赞、收藏。
转载自:https://juejin.cn/post/7386102969257394185