• Spring AOP高级——源码实现(2)Spring AOP中通知器(Advisor)与切面(Aspect)


    本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/Spring%20AOP%E9%AB%98%E7%BA%A7%E2%80%94%E2%80%94%E6%BA%90%E7%A0%81%E5%AE%9E%E7%8E%B0%EF%BC%882%EF%BC%89Spring%20AOP%E4%B8%AD%E9%80%9A%E7%9F%A5%E5%99%A8%EF%BC%88Advisor%EF%BC%89%E4%B8%8E%E5%88%87%E9%9D%A2%EF%BC%88Aspect%EF%BC%89

      之所以还未正式进入Spring AOP的源码,是因为我在阅读Spring AOP生成代理对象时遇到了一点小麻烦让我不得不暂时停止,转而理清有关Spring AOP中的两个概念性问题。

      前面的博客里都没有提到过“通知器”这个概念,在《Spring实战》书中也只是简单地说明了在xml中<aop:advisor>用于定义一个通知器,此后便没再说明,而是使用<aop:aspect>定义一个切面。而在《Spring技术内幕》中有关Spring AOP章节中则是介绍了AOP中三个概念:通知、切点、通知器。在这时,我对“通知器”产生了很大的疑惑,查阅了相关资料并没有满意的答案,于是决定自己一探究竟。

      首先来讨论定义通知器相关的使用方法。 定义一个通知类,其中包含前置通知和后置通知,注意如果是使用<aop:advisor>定义通知器的方式实现AOP则需要通知类实现Advice接口,前置通知方法对应的是MethodBeforeAdvice,后置通知方法对应的是AfterReturningAdvice。

     1 package com.demo;
     2 
     3 import org.springframework.aop.AfterReturningAdvice;
     4 import org.springframework.aop.MethodBeforeAdvice;
     5 import org.springframework.stereotype.Component;
     6 
     7 import java.lang.reflect.Method;
     8 
     9 /**
    10  * Created by Kevin on 2017/11/15.
    11  */
    12 @Component("advisorTest")
    13 public class AdvisorTest implements MethodBeforeAdvice, AfterReturningAdvice{
    14 
    15     /**
    16      * 前置通知
    17      * @param method
    18      * @param args
    19      * @param target
    20      * @throws Throwable
    21      */
    22     @Override
    23     public void before(Method method, Object[] args, Object target) throws Throwable {
    24         System.out.println("前置通知");
    25     }
    26 
    27     /**
    28      * 后置通知
    29      * @param returnValue
    30      * @param method
    31      * @param args
    32      * @param target
    33      * @throws Throwable
    34      */
    35     @Override
    36     public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
    37         System.out.println("后置通知");
    38     }
    39 }

      定义一个需要被代理的目标对象。

     1 package com.demo;
     2 
     3 import org.springframework.stereotype.Component;
     4 
     5 /**
     6  * 目标对象,需要被代理的类及方法
     7  * Created by Kevin on 2017/11/15.
     8  */
     9 @Component("testPoint")
    10 public class TestPoint {
    11 
    12     public void test() {
    13         System.out.println("方法调用");
    14     }
    15 }

      我们要达到的目的就是在test方法调用前和调用后分别打印“前置通知”和“后置通知”。

      applicationContext.xml中定义通知器如下:

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4        xmlns:context="http://www.springframework.org/schema/context"
     5        xmlns:aop="http://www.springframework.org/schema/aop"
     6        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
     7 
     8     <context:component-scan base-package="com.demo"/>
     9 
    10     <aop:config>
    11         <aop:pointcut id="test" expression="execution(* com.demo.TestPoint.test())"/>
    12         <aop:advisor advice-ref="advisorTest" pointcut-ref="test"/>
    13     </aop:config>
    14 
    15 </beans>

      最后的运行结果符合预期。那么问题来了,如果我们只想在定义的这个切点 <aop:pointcut id="test" expression="execution(* com.demo.TestPoint.test())"/>里只配置前置通知,这个时候怎么办呢?答案是,通过以上方式是不可以的。也就是说如果通过定义Advisor的方式,在有的地方比较局限,狭隘来讲通过定义Advisor通知器的方式,只能定义只有一个通知和一个切入点的切面。当然一个通知不准确,因为上面可以看到只要实现不同的通知接口即可代理,但如果实现了多个通知接口,而只想使用一个时就不可以了。通知器是一个特殊的切面。

      接着来讨论定义切面相关的使用方法。 如果使用<aop:aspect>定义切面的方式,通知类是可以不用实现任何通知接口的,这是很大一个便利。同样要实现上面例子的功能,定义一个通知类,包括前置通知和后置通知。

     1 package com.demo;
     2 
     3 import org.springframework.stereotype.Component;
     4 
     5 /**
     6  * Created by Kevin on 2017/11/15.
     7  */
     8 @Component("aspectTest")
     9 public class AspectTest {
    10 
    11     /**
    12      * 前置通知
    13      */
    14     public void doBefore() {
    15         System.out.println("前置通知");
    16     }
    17 
    18     /**
    19      * 后置通知
    20      */
    21     public void doAfter() {
    22         System.out.println("后置通知");
    23     }
    24 }

      目标对象和上面的例子一致,紧接着是applicationContext.xml中切面的配置。

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4        xmlns:context="http://www.springframework.org/schema/context"
     5        xmlns:aop="http://www.springframework.org/schema/aop"
     6        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
     7 
     8     <context:component-scan base-package="com.demo"/>
     9 
    10     <aop:config>
    11         <aop:aspect ref="aspectTest">
    12             <aop:pointcut id="test" expression="execution(* com.demo.TestPoint.test())"/>
    13             <aop:before method="doBefore" pointcut-ref="test"/>
    14             <aop:after-returning method="doAfter" pointcut-ref="test"/>
    15         </aop:aspect>
    16     </aop:config>
    17 </beans>

      可以看到我们通过<aop:aspect>定义了一个切面,如果只需要前置通知,则只定义<aop:before>就可以了,这和<aop:advisor>是很大的不同,由此可知通过<aop:aspect>定义切面的方式可以在其中灵活地定义通知,而不必像通知器那样约束。

      实际上可以这么说,通知器是一个特殊的切面。而在最开始那两篇博客中没有提到是因为那两个例子中使用的是AspectJ注解,而在AspectJ注解中并没有与此对应的概念。

      在实际中用到的<aop:advisor>场景最多的莫过于在Spring中配置事务。除此之外,很少用到,也不建议使用。因为最大的一个问题就是定义通知时需要实现通知接口,这违背了一点Spring“非侵入式”编程的初衷。

      这篇博客穿插在源码的其中是为了更好的理清Spring AOP中各种概念问题,缘由我在开头已经说过,接下来就正式开始Spring AOP源码的解读。

    这是一个能给程序员加buff的公众号 

  • 相关阅读:
    进制
    流程控制
    运算符
    格式化输出
    数据结构-树的遍历
    A1004 Counting Leaves (30分)
    A1106 Lowest Price in Supply Chain (25分)
    A1094 The Largest Generation (25分)
    A1090 Highest Price in Supply Chain (25分)
    A1079 Total Sales of Supply Chain (25分)
  • 原文地址:https://www.cnblogs.com/yulinfeng/p/7841167.html
Copyright © 2020-2023  润新知