zoukankan      html  css  js  c++  java
  • Spring入门之AOP实践:@Aspect + @Pointcut + @Before / @Around / @After

    零、准备知识

    1)AOP相关概念:Aspect、Advice、Join point、Pointcut、Weaving、Target等。
    2)相关注解:@Aspect、@Pointcut、@Before、@Around、@After、@AfterReturning、@AfterThrowing
     

    一、实践目标

    1)@Aspect的功能
    2)@Pointcut的切面表达式
    3)@Before、@Around、@After、@AfterReturning / @AfterThrowing的时序关系
    4)AOP概念的重新梳理和使用
     

    二、核心代码

    MainController.java包含两个测试函数,分别是doSomething() 和 test()。
     1 // MainController.java
     2 @RestController
     3 public class MainController {
     4     RequestMapping(value="/doSomething", method = RequestMethod.POST)
     5     @CrossOrigin("*")
     6     public void doSomething() { 
     7         System.out.println("This is doSomething"); 
     8         test();
     9     }
    10 
    11     @RequestMapping(value="/justTest", method = RequestMethod.POST)
    12     @CrossOrigin("*")
    13     public void test() { System.out.println("This is test");}
    14 }

    ExampleAop.java为AOP相关代码,定义了pointcut和多个Advice函数。

     1 // ExampleAop.java
     2 @Component
     3 @Aspect
     4 @Order(1)
     5 public class ExampleAop {
     6 
     7     private static final Logger LOGGER = LoggerFactory.getLogger(ExampleAop.class);
     8 
     9     // 匹配com.example.demo包及其子包下的所有类的所有方法
    10     @Pointcut("execution(* com.example.demo..*.*(..))")
    11     public void executeService() {
    12     }
    13 
    14     @Before("executeService()")
    15     public void doBeforeAdvice(JoinPoint joinPoint) throws Exception {
    16         LOGGER.info("Before [{}]", joinPoint.getSignature().getName());
    17     }
    18 
    19     @Around("executeService()")
    20     public void doAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    21         LOGGER.info("Around1 [{}]", joinPoint.getSignature().getName());
    22         joinPoint.proceed();
    23         LOGGER.info("Around2 [{}]", joinPoint.getSignature().getName());
    24     }
    25 
    26     @After("executeService()")
    27     public void doAfterAdvice(JoinPoint joinPoint) throws Exception {
    28         LOGGER.info("After [{}]", joinPoint.getSignature().getName());
    29     }
    30 
    31     @AfterReturning("executeService()")
    32     public void doAfterReturningAdvice(JoinPoint joinPoint) throws Exception {
    33         LOGGER.info("AfterReturning [{}]", joinPoint.getSignature().getName());
    34     }
    35 }

    编译并运行jar包,调用接口/doSomething。

    1 # 调用接口
    2 curl localhost:8080/doSomething

    到服务器观察日志。

    1 <!-- 后台日志 -->
    2 com.example.demo.aop.ExampleAop          : Around1 [doSomething]
    3 com.example.demo.aop.ExampleAop          : Before [doSomething]
    4 This is doSomething
    5 This is test
    6 com.example.demo.aop.ExampleAop          : Around2 [doSomething]
    7 com.example.demo.aop.ExampleAop          : After [doSomething]
    8 com.example.demo.aop.ExampleAop          : AfterReturning [doSomething]

    三、分析与结论

    1)@Aspect的功能
      在连接点的前后添加处理。在本例中,doSomething() 是连接点,而test() 不是。
      是否只有最外层的joinpoint才会被Advice插入?在后面进行简单的探讨和猜测。
     
     
    2)@Pointcut的切面表达式
      ref: https://www.jianshu.com/p/fbbdebf200c9  完整表达式
      @Pointcut("execution(...)") 是Pointcut表达式,executeService() 是point签名。表达式中可以包含签名的逻辑运算。
     
      常用表达式:
    1 execution(public * com.example.demo.ExampleClass.*(..))  // ExampleClass的所有公有方法
    2 execution(* com.example.demo..*.*(..)) // com.example.demo包及其子包下的所有方法
    3 logSender() || logMessage() // 两个签名的表达式的并集
    3)@Before、@Around、@After、@AfterReturning / @AfterThrowing的时序关系
      @Around1 -> @Before -> 方法 -> @Around2 -> @After -> @AfterReturning / @AfterThrowing(时序问题后面有额外讨论。)
      另外可以发现,@Around是可以影响程序本身执行的,如果不调用 joinPoint.proceed(); 就不会执行方法。其他几个都无法影响程序执行。
     
     
    4)AOP概念的重新梳理和使用
      Aspect(切面):使用了@Aspect注解,如ExampleAop类。
      Advice(增强):在指定位置进行的增强操作,如方法运行时间统计、用户登录、日志记录等。由@Before、@After等注解标注,如doBeforeAdvice() 方法。
      Weaving(织入):AOP就是一种把Advice织入(即嵌入、插入)到Aspect中指定位置执行的机制。
      Join point(连接点):Advice执行的位置,也是Advice的参数,是一个具体的方法。如日志中看到的doSomething() 函数。
      Pointcut(切点):以表达式的形式表示一组join point,用于由@Pointcut注解定义Advice的作用位置。如@Pointcut("execution(* com.example.demo..*.*(..))") 代表com.example.demo包及其子包下的所有类的所有方法。
      Target(对象):被增强的对象,即包含主业务逻辑的类的对象,如ExampleAop类的实例。

    四、疑问与讨论

    1. 本文说执行顺序为@Around1 -> @Before -> 方法 -> @Around2 -> @After,但有的文章中说是@Before -> @Around1 -> 方法 -> @Around2 -> @After,也有说@Around1 -> @Before -> 方法 -> @After -> @Around2,哪个对?
     
      反正代码跑起来是这个顺序,那就是这个顺序喽。每个Advice都加上sleep拉开时间也没有变化。不知道是否受版本或代码自身影响。
      总之可以得到一个结论:@Before / @After 最好不要和@Around混用,执行顺序不好确定。
      时序至少总是满足:@Around1 / @Before -> 方法 -> @Around2 / @After -> @AfterReturning / @AfterThrowing
      另外找到一篇支持本文执行顺序的文章:https://blog.csdn.net/qq_32331073/article/details/80596084
     
     
    2. 为何doSomething() 和 test() 都是@Pointcut中选中的作用节点,但只有doSomething() 插入了Advice,而test() 没有呢?
     
            一个猜测:从字面意思理解,@Pointcut对所有代码以表达式为规则剪一刀,一侧是所有的joinpoint,另一侧是普通代码。在joinpoint与另一侧代码间插入一层Advice的代理,另一侧的代码如果要调用joinpoint,则必须经Advice进行增强操作。而不同的joinpoint在同一侧,因此未插入Advice。
            有时间再读源码了解其中的机制。

    (一个猜测)

    五、Future Work

    1. AOP中还有Advisor等概念,待学习。
    2. 切面表达式(@Pointcut里的表达式)规则丰富,待学习。
    3. Advice的插入时机,待读源码学习。
  • 相关阅读:
    GPU编程和流式多处理器(七)
    GPU编程和流式多处理器(六)
    vue——使用vant轮播组件swipe + flex时,文字抖动问题
    golang 修改字符串
    Go 彻底弄懂return和defer的微妙关系
    Redis 的持久化机制
    Redis 缓存击穿
    Redis 缓存穿透
    Redis 雪崩
    正则验证
  • 原文地址:https://www.cnblogs.com/desertfish/p/11421260.html
Copyright © 2011-2022 走看看