搜索
您的当前位置:首页正文

Spring AOP(五)切入点和通知

来源:二三娱乐

本文主要描述 Spring AOP 中的 PointcutAdvice 接口。

我们从 ProxyFactory 类开始说起,先来看一个简单的 Demo。

public class ProxyFactoryDemo {

    public static void main(String[] args) {
        // 1. 构造目标对象
        Cat catTarget = new Cat();

        // 2. 通过目标对象,构造 ProxyFactory 对象
        ProxyFactory factory = new ProxyFactory(catTarget);

        // 添加一个方法拦截器
        factory.addAdvice(new MyMethodInterceptor());

        // 3. 根据目标对象生成代理对象
        Object proxy = factory.getProxy();

        Animal cat = (Animal) proxy;
        cat.eat();
    }

    public static class MyMethodInterceptor implements MethodInterceptor {

        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("MyMethodInterceptor invoke 调用 before invocation.proceed");

            Object ret = invocation.proceed();

            System.out.println("MyMethodInterceptor invoke 调用 after invocation.proceed");
            return ret;
        }
    }
}

运行上面的 main 方法,结果如下所示。

输出结果.jpg

源码分析

首先,我们先来看一下 ProxyFactory 类的 Object target 参数构造函数。

public ProxyFactory(Object target) {
  // 1. 设置目标对象,在创建代理对象以及调用代理方法时会用到
  setTarget(target);
  // 2. 设置代理类需要实现的接口,也就是目标类实现的所有接口和其父接口
  setInterfaces(ClassUtils.getAllInterfaces(target));
}

在讨论 addAdvice 方法之前,我们先来看一下 org.aopalliance.intercept.MethodInterceptor 的 UML 类图。

MethodInterceptor UML 关系.jpg

然后我们来看一下 addAdvice 的方法内部实现。

public void addAdvice(Advice advice) throws AopConfigException {
  int pos = this.advisors.size();
  addAdvice(pos, advice);
}

public void addAdvice(int pos, Advice advice) throws AopConfigException {
  Assert.notNull(advice, "Advice must not be null");
  // 添加一个 DefaultPointcutAdvisor 到 Advisor 集合
  addAdvisor(pos, new DefaultPointcutAdvisor(advice));
}

DefaultPointcutAdvisor UML 类图如下所示。

PointcutAdvisor UML 关系.jpg

简单介绍下上图中出现接口和类:

  • Advisor:持有 org.aopalliance.aop.Advice 对象的基础接口,可以简单的看作是 org.aopalliance.aop.Advice 在 Spring 中的等价接口。

  • PointcutAdvisor:包含切入点的 Advisor,从而可以针对符合 Pointcut 规则的连接点进行增强处理。

  • Ordered:用来确定当前 Advisor 在拦截器责任链列表中的位置,主要用在 Aspect 中。

切入点 API

Spring 的 Pointcut 模型使切入点独立于 Advisor(或者说是 Advice) 类型。您可以使用相同的切入点来对方法做不能的增强处理。

org.springframework.aop.Pointcut 接口是核心接口,用来将 Advice 应用到特定的类和方法。完整的接口如下:

package org.springframework.aop;

/**
 * 一个切入点由 ClassFilter 和 MethodMatcher 组成。
 */
public interface Pointcut {

  ClassFilter getClassFilter();

  MethodMatcher getMethodMatcher();
}

Pointcut 接口拆分为两部分允许重用 ClassFilterMethodMatcher 部分以及细粒度合成操作(可以查看 UnionClassFilterIntersectionClassFilterUnionMethodMatcherIntersectionMethodMatcher 等类的实现细节)。

ClassFilter 接口用于将切入点限制为给定的一组目标类。如果 matches() 方法始终返回 true,则匹配所有目标类。以下清单显示了 ClassFilter 接口定义:

package org.springframework.aop;

public interface ClassFilter {

  /**
   * 切入点是否应用于给定的接口或目标类?
   * @param clazz 目标类
   * @return 该 Pointcut 关联的 Advice 是否需要适用于给定的目标类
   */
  boolean matches(Class<?> clazz);

}

MethodMatcher 接口通常更重要。完整的接口清单如下所示:

package org.springframework.aop;

/**
 * 检查 Pointcut 关联的 Advice 是否需要适用于给定的目标方法
 */
public interface MethodMatcher {

  /**
   * 对方法进行静态匹配,即不对方法调用的传入的实参进行校验
   */
  boolean matches(Method method, Class<?> targetClass);

  /**
   * 返回当前 MethodMatcher 是否需要进行动态匹配。
   *   如果 isRuntime() 方法返回 true,则表示需要调用 matches(Method, Class, Object[])方法对目标方法进行匹配
   */
  boolean isRuntime();

  /**
   * 对方法进行动态匹配,即对方法调用的传入的实参进行校验
   */
  boolean matches(Method method, Class<?> targetClass, Object... args);

}

matches(Method, Class) 方法用于测试此切入点是否与目标类上的给定方法匹配。

如果双参数 matches 方法返回 true,并且 MethodMatcherisRuntime() 方法返回 true,则在每次方法调用时都会调用三参数 matches(Method, Class, Object[]) 方法。这使得切入点可以在执行目标 Advice 做增强处理之前通过传递给方法调用的参数对方法进行匹配。

大多数 MethodMatcher 实现是静态的,这意味着它们的 isRuntime() 方法返回 false。在这种情况下,matches 永远不会调用三参数方法。

Advisor API

在 Spring 中,Advisor 是一个切面,它包含与切入点表达式关联的单个 Advice 对象。

除了介绍的特殊情况,任何 Advisor 都可以使用任何 Adviceorg.springframework.aop.support.DefaultPointcutAdvisor 是最常用的 Advisor 类。它可以与使用MethodInterceptorBeforeAdviceThrowsAdvice

可以在同一个 AOP 代理中混合 Spring 中的 AdvisorAdvice 类型。例如,您可以在一个代理配置中使用拦截建议,抛出建议和建议之前。Spring 自动创建必要的拦截链。

我们继续来看 DefaultPointcutAdvisor 类的构造方法代码清单。

// TruePointcut 对象,表示匹配所有类的所有方法,即对所有方法进行增强处理
private Pointcut pointcut = Pointcut.TRUE;

public DefaultPointcutAdvisor(Advice advice) {
  this(Pointcut.TRUE, advice);
}

public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
  this.pointcut = pointcut;
  setAdvice(advice);
}

由此我们可以得知,通过 factory.addAdvice 添加的 Advice 会对目标对象的所有方法进行增强处理。

我们修改 Animal 接口,为其增加 go 方法定义,并在 Cat 类中实现 go 方法,代码清单如下所示。

public interface Animal {
    void eat();
    void go();
}

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    @Override
    public void go() {
        System.out.println("猫在跑");
    }
}

然后我们在 ProxyFactoryDemo 类的 main 方法中,新增对其 go 方法的调用逻辑。

public static void main(String[] args) {
  // 1. 构造目标对象
  Animal catTarget = new Cat();

  // 2. 通过目标对象,构造 ProxyFactory 对象
  ProxyFactory factory = new ProxyFactory(catTarget);

  // 添加一个方法拦截器
  factory.addAdvice(new MyMethodInterceptor());

  // 3. 根据目标对象生成代理对象
  Object proxy = factory.getProxy();

  Animal cat = (Animal) proxy;
  System.out.println(cat.getClass());
  cat.eat();

  System.out.println("---------------------------------------");

  cat.go();
}

运行 ProxyFactoryDemo 类的 main 方法,我们可以得到如下图所示的打印结果。

输出结果.jpg

接下来,我们定义一个 Pointcut 接口的实现类 MyPointcut,代码如下所示。

public class MyPointcut implements Pointcut {
    @Override
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                // 匹配所有的类
                return true;
            }
        };
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        // 继承 StaticMethodMatcher,忽略方法实参,只对方法进行动态匹配。
        return new StaticMethodMatcher() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                // 如果方法名称是 go,则匹配,否则不匹配
                if (method.getName().equals("go")) {
                    return true;
                }
                return false;
            }
        };
    }
}

然后定义一个 PointcutAdvisor 接口的实现类 MyPointcutAdvisor,代码清单如下所示。

public class MyPointcutAdvisor implements PointcutAdvisor {

    private Pointcut pointcut = new MyPointcut();

    private Advice advice;

    public MyPointcutAdvisor(Advice advice) {
        this.advice = advice;
    }

    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    @Override
    public Advice getAdvice() {
        return this.advice;
    }

    /**
     * 此方法暂时忽略,不需要理会
     */
    @Override
    public boolean isPerInstance() {
        return false;
    }
}

然后我们修改 ProxyFactoryDemo 类的 main 方法中的逻辑,修改后的方法如下所示。

public static void main(String[] args) {
  // 1. 构造目标对象
  Animal catTarget = new Cat();

  // 2. 通过目标对象,构造 ProxyFactory 对象
  ProxyFactory factory = new ProxyFactory(catTarget);

  // 添加一个 Advice (DefaultPointcutAdvisor)
  factory.addAdvice(new MyMethodInterceptor());

  // 新增代码:添加一个 PointcutAdvisor
  MyPointcutAdvisor myPointcutAdvisor = new MyPointcutAdvisor(new MyMethodInterceptor());
  factory.addAdvisor(myPointcutAdvisor);

  // 3. 根据目标对象生成代理对象
  Object proxy = factory.getProxy();

  Animal cat = (Animal) proxy;
  System.out.println(cat.getClass());
  cat.eat();

  System.out.println("---------------------------------------");

  cat.go();
}

运行 ProxyFactoryDemo 类的 main 方法,我们可以得到如下图所示的打印结果。

输出结果.jpg

由上图可知,eat 方法被增强了一次,而 go 方法被增强了两次,说明我们自定义的切入点 MyPointcut 已经生效。

至此,PointcutAdviceAdvisor) 以及 PointcutAdvisor 的整体架构脉络我们就都清楚了。

参考文献

(正文完)

扩展阅读

喜欢本文的朋友们,欢迎关注微信公众号【程序员小课堂】,阅读更多精彩内容!


程序员小课堂.jpg
Top