likes
comments
collection
share

【重写SpringFramework】第二章aop模块:单通知切面Advisor(chapter 2-3)

作者站长头像
站长
· 阅读数 17

1. 前言

Spring AOP 关注的是方法拦截,扩展了 Advice 接口,实现了对方法的增强。理论上,Advice 可以对所有的方法进行增强,但这显然不是我们所期望的。AOP 理念的核心是需求驱动服务,只对有需要的方法进行增强,而拦截所有方法只是一种特殊情况。鉴于此,我们需要一种灵活的方式来动态地标定适用范围。

从几何学的视角来看,如果把单个方法视为一维的点,那么将若干方法连缀起来就构成了二维的面,这个面就是通常所说的切面。Spring 提供了两种构建切面的方式,一是切点(pointcut),二是引入(introduction)。此外,Spring 为了将增强逻辑与切面特性整合在一起,提供了 Advisor 接口。

2. 切点

2.1 概述

PointcutMethodMatcherClassFilter 组成,也就是说切点是对指定的类和方法进行匹配的。ClassFilterMethodMatcher 两个组件的配合是比较灵活的,可以让其中一个起作用,也可以让两者都起作用。注意,这里起作用的意思是指有效拦截(体现切面的特性) 。因为 ClassFilterMethodMatcher 都有一个特殊实例,默认拦截所有的类或方法。

【重写SpringFramework】第二章aop模块:单通知切面Advisor(chapter 2-3)

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

StaticMethodMatcherPointcutPointcut 接口的抽象子类,从类名可以看出,侧重点是静态的方法匹配。从代码中也可以看出端倪,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 是一个典型的切点实现类,由一个类过滤器和一个方法匹配器组成。

【重写SpringFramework】第二章aop模块:单通知切面Advisor(chapter 2-3)

AnnotationMatchingPointcut 实现了 Pointcut 接口,作用是检查类或方法上是否声明了指定的注解。从构造方法可以看到,具体的匹配逻辑是由 AnnotationClassFilterAnnotationMethodMatcher 两个组件完成的。

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 作为统一的抽象,来整合 AdviceMethodInterceptorAdvisor 等不同类型的通知,这既是所谓的通用性。

3.2 继承结构

Advisor 的继承结构分为两个部分,划分依据是构建切面的方式,也就是引入和切点。其中,IntroductionAdvisor 接口的作用是通过引入的方式进行拦截,仅了解即可。我们关心的是以 Pointcut 方式构成的切面,因此着重来看 PointcutAdvisor 是如何实现的。

【重写SpringFramework】第二章aop模块:单通知切面Advisor(chapter 2-3)

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 作为兜底的存在,目的是对 AdviceMethodInterceptor 进行适配。

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 除了增强和切面这两个基本功能外,还可以作为统一的抽象,来整合 AdviceAdvisorInterceptor 等不同类型的通知。如下图所示,整个继承结构可以分为两个部分,一是以 AdvisorAdapter 接口为代表的适配器对象,二是通过 AdvisorAdapterRegistry 接口注册适配器,并提供转换的功能。

【重写SpringFramework】第二章aop模块:单通知切面Advisor(chapter 2-3)

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 方法将 MethodInterceptorAdvice 都包装成了 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 对象,然后分别注册了一个 MethodInterceptorMethodBeforeAdviceAfterReturningAdvice 接口的实现类,它们最终都被统一适配成 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 提供了两种构建切面的方式,一是切点,二是引入。为了简化代码,我们只关心切点,引入仅了解即可。切点实际上是一个组合体,由 ClassFilterMethodMatcher 构成,前者对类进行过滤,后者对方法进行匹配。大多数情况下,我们只关心方法的匹配。方法匹配又分为静态匹配和动态匹配,所谓静态匹配是指方法签名,包括方法上的注解之类,这些信息在编译期就能确定。所谓动态匹配是指除了静态匹配的内容,还要匹配运行时传入方法的参数。经过不断地瘦身,构建切面的主要途径是对方法进行静态匹配

本节还介绍了另一个重要的组件 Advisor,该组件有三个作用。其一,将 Advice 与切点/引入组合在一起,拥有了拦截语义和切面特性,这是构成 AOP 的基础。其二,持有一个 Advice 实例,说明一个 Advisor 只能实现一种增强逻辑。一个应用中可能需要多个 AOP 功能,则需要定义多个 Advisor。其三,通知对象分为三种,AdviceMethodInterceptorAdvisor,需要进行统一的适配。

【重写SpringFramework】第二章aop模块:单通知切面Advisor(chapter 2-3)

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 (+)

注:+号表示新增、*表示修改

注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。


欢迎关注公众号【Java编程探微】,加群一起讨论。

原创不易,觉得内容不错请关注、点赞、收藏。

转载自:https://juejin.cn/post/7386102969257394185
评论
请登录