美文网首页SpringFramework
Spring AOP(五)切入点和通知

Spring AOP(五)切入点和通知

作者: 蓝笔头 | 来源:发表于2019-05-07 13:02 被阅读97次

    本文主要描述 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

    注意org.aopalliance.intercept.MethodInterceptor 不是 Spring AOP (三) CGLIB 动态代理 中的 net.sf.cglib.proxy.MethodInterceptor 接口,但是和 Spring AOP (四) 多重代理和责任链模式 中定义的 MyMethodInterceptor 接口类似。

    然后我们来看一下 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

    相关文章

      网友评论

        本文标题:Spring AOP(五)切入点和通知

        本文链接:https://www.haomeiwen.com/subject/nnusoqtx.html