聊聊Spring AOP @Before @Around @After等advice的执行顺序

用过spring框架进行开发的人,多多少少会使用过它的AOP功能,都知道有@Before、@Around和@After等advice。

最近,为了实现项目中的输出日志和权限控制这两个需求,我也使用到了AOP功能。

我使用到了@Before、@Around这两个advice。但在,使用过程中,却对它们的执行顺序并不清楚。

为了弄清楚在不同情况下,这些advice到底是以怎么样的一个顺序进行执行的,我作了个测试,在此将其记录下来,以供以后查看。

前提

对于AOP相关类(aspect、pointcut等)的概念,本文不作说明。

对于如何让spring框架扫描到AOP,本文也不作说明。

情况一: 一个方法只被一个Aspect类拦截

当一个方法只被一个Aspect拦截时,这个Aspect中的不同advice是按照怎样的顺序进行执行的呢?请看:

添加 PointCut类

该pointcut用来拦截test包下的所有类中的所有方法。

package test;
import org.aspectj.lang.annotation.Pointcut;
public class PointCuts {
 @Pointcut(value = "within(test.*)")
 public void aopDemo() {
 }
}

添加Aspect类

该类中的advice将会用到上面的pointcut,使用方法请看各个advice的value属性。

package test;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Aspect1 {
 @Before(value = "test.PointCuts.aopDemo()")
 public void before(JoinPoint joinPoint) {
  System.out.println("[Aspect1] before advise");
 }
 @Around(value = "test.PointCuts.aopDemo()")
 public void around(ProceedingJoinPoint pjp) throws Throwable{
  System.out.println("[Aspect1] around advise 1");
  pjp.proceed();
  System.out.println("[Aspect1] around advise2");
 }
 @AfterReturning(value = "test.PointCuts.aopDemo()")
 public void afterReturning(JoinPoint joinPoint) {
  System.out.println("[Aspect1] afterReturning advise");
 }
 @AfterThrowing(value = "test.PointCuts.aopDemo()")
 public void afterThrowing(JoinPoint joinPoint) {
  System.out.println("[Aspect1] afterThrowing advise");
 }
 @After(value = "test.PointCuts.aopDemo()")
 public void after(JoinPoint joinPoint) {
  System.out.println("[Aspect1] after advise");
 }
}

添加测试用Controller

添加一个用于测试的controller,这个controller中只有一个方法,但是它会根据参数值的不同,会作出不同的处理:一种是正常返回一个对象,一种是抛出异常(因为我们要测试@AfterThrowing这个advice)

package test;
import test.exception.TestException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/aop")
public class AopTestController {
 @ResponseStatus(HttpStatus.OK)
 @RequestMapping(value = "/test", method = RequestMethod.GET)
 public Result test(@RequestParam boolean throwException) {
  // case 1
  if (throwException) {
   System.out.println("throw an exception");
   throw new TestException("mock a server exception");
  }
  // case 2
  System.out.println("test OK");
  return new Result() {{
   this.setId(111);
   this.setName("mock a Result");
  }};
 }
 public static class Result {
  private int id;
  private String name;
  public int getId() {
   return id;
  }
  public void setId(int id) {
   this.id = id;
  }
  public String getName() {
   return name;
  }
  public void setName(String name) {
   this.name = name;
  }
 }
}

测试 正常情况

在浏览器直接输入以下的URL,回车:

http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=false

我们会看到输出的结果是:

[Aspect1] around advise 1
[Aspect1] before advise
test OK
[Aspect1] around advise2
[Aspect1] after advise
[Aspect1] afterReturning advise

测试 异常情况

在浏览器中直接输入以下的URL,回车:

http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=true

我们会看到输出的结果是:

[Aspect1] around advise 1
[Aspect1] before advise
throw an exception
[Aspect1] after advise
[Aspect1] afterThrowing advise

结论

在一个方法只被一个aspect类拦截时,aspect类内部的 advice 将按照以下的顺序进行执行:

正常情况:

异常情况:

情况二: 同一个方法被多个Aspect类拦截

此处举例为被两个aspect类拦截。

有些情况下,对于两个不同的aspect类,不管它们的advice使用的是同一个pointcut,还是不同的pointcut,都有可能导致同一个方法被多个aspect类拦截。那么,在这种情况下,这多个Aspect类中的advice又是按照怎样的顺序进行执行的呢?请看:

pointcut类保持不变

添加一个新的aspect类

package test;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Aspect2 {
 @Before(value = "test.PointCuts.aopDemo()")
 public void before(JoinPoint joinPoint) {
  System.out.println("[Aspect2] before advise");
 }
 @Around(value = "test.PointCuts.aopDemo()")
 public void around(ProceedingJoinPoint pjp) throws Throwable{
  System.out.println("[Aspect2] around advise 1");
  pjp.proceed();
  System.out.println("[Aspect2] around advise2");
 }
 @AfterReturning(value = "test.PointCuts.aopDemo()")
 public void afterReturning(JoinPoint joinPoint) {
  System.out.println("[Aspect2] afterReturning advise");
 }
 @AfterThrowing(value = "test.PointCuts.aopDemo()")
 public void afterThrowing(JoinPoint joinPoint) {
  System.out.println("[Aspect2] afterThrowing advise");
 }
 @After(value = "test.PointCuts.aopDemo()")
 public void after(JoinPoint joinPoint) {
  System.out.println("[Aspect2] after advise");
 }
}

测试用Controller也不变

还是使用上面的那个Controller。但是现在 aspect1 和 aspect2 都会拦截该controller中的方法。

下面继续进行测试!

测试 正常情况

在浏览器直接输入以下的URL,回车:

http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=false

我们会看到输出的结果是:

[Aspect2] around advise 1
[Aspect2] before advise
[Aspect1] around advise 1
[Aspect1] before advise
test OK
[Aspect1] around advise2
[Aspect1] after advise
[Aspect1] afterReturning advise
[Aspect2] around advise2
[Aspect2] after advise
[Aspect2] afterReturning advise

但是这个时候,我不能下定论说 aspect2 肯定就比 aspect1 先执行。

不信?你把服务务器重新启动一下,再试试,说不定你就会看到如下的执行结果:

[Aspect1] around advise 1
[Aspect1] before advise
[Aspect2] around advise 1
[Aspect2] before advise
test OK
[Aspect2] around advise2
[Aspect2] after advise
[Aspect2] afterReturning advise
[Aspect1] around advise2
[Aspect1] after advise
[Aspect1] afterReturning advise

也就是说,这种情况下, aspect1 和 aspect2 的执行顺序是未知的。那怎么解决呢?不急,下面会给出解决方案。

测试 异常情况

在浏览器中直接输入以下的URL,回车:

http://192.168.142.8:7070/aoptest/v1/aop/test?throwException=true

我们会看到输出的结果是:

[Aspect2] around advise 1
[Aspect2] before advise
[Aspect1] around advise 1
[Aspect1] before advise
throw an exception
[Aspect1] after advise
[Aspect1] afterThrowing advise
[Aspect2] after advise
[Aspect2] afterThrowing advise

同样地,如果把服务器重启,然后再测试的话,就可能会看到如下的结果:

[Aspect1] around advise 1
[Aspect1] before advise
[Aspect2] around advise 1
[Aspect2] before advise
throw an exception
[Aspect2] after advise
[Aspect2] afterThrowing advise
[Aspect1] after advise
[Aspect1] afterThrowing advise

也就是说,同样地,异常情况下, aspect1 和 aspect2 的执行顺序也是未定的。

那么在 情况二 下,如何指定每个 aspect 的执行顺序呢?

方法有两种:

实现org.springframework.core.Ordered接口,实现它的getOrder()方法

给aspect添加@Order注解,该注解全称为:org.springframework.core.annotation.Order

不管采用上面的哪种方法,都是值越小的 aspect 越先执行。

比如,我们为 apsect1 和 aspect2 分别添加 @Order 注解,如下:

@Order(5)
@Component
@Aspect
public class Aspect1 {
 // ...
}
@Order(6)
@Component
@Aspect
public class Aspect2 {
 // ...
}

这样修改之后,可保证不管在任何情况下, aspect1 中的 advice 总是比 aspect2 中的 advice 先执行。

如下图所示:

注意点

如果在同一个 aspect 类中,针对同一个 pointcut,定义了两个相同的 advice(比如,定义了两个 @Before),那么这两个 advice 的执行顺序是无法确定的,哪怕你给这两个 advice 添加了 @Order 这个注解,也不行。这点切记。

对于@Around这个advice,不管它有没有返回值,但是必须要方法内部,调用一下 pjp.proceed();否则,Controller 中的接口将没有机会被执行,从而也导致了 @Before这个advice不会被触发。

比如,我们假设正常情况下,执行顺序为”aspect2 -> apsect1 -> controller”,如果,我们把 aspect1中的@Around中的 pjp.proceed();给删掉,那么,我们看到的输出结果将是:

[Aspect2] around advise 1
[Aspect2] before advise
[Aspect1] around advise 1
[Aspect1] around advise2
[Aspect1] after advise
[Aspect1] afterReturning advise
[Aspect2] around advise2
[Aspect2] after advise
[Aspect2] afterReturning advise

从结果可以发现, Controller 中的 接口 未被执行,aspect1 中的 @Before advice 也未被执行。

参考资料

Spring 4.3.2.RELEASE 官方资料:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/

其中,AOP的执行顺序章节为:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#aop-ataspectj-advice-ordering

Advice ordering

What happens when multiple pieces of advice all want to run at the same join point?

Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution.

The highest precedence advice runs first "on the way in" (so given two pieces of before advice, the one with highest precedence runs first).

"On the way out" from a join point, the highest precedence advice runs last (so given two pieces of after advice, the one with the highest precedence will run second).

When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise the order of execution is undefined.

You can control the order of execution by specifying precedence.

This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the aspect class or annotating it with the Order annotation.

Given two aspects, the aspect returning the lower value from Ordered.getValue() (or the annotation value) has the higher precedence.

When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via reflection for javac-compiled classes).

Consider collapsing such advice methods into one advice method per join point in each aspect class, or refactor the pieces of advice into separate aspect classes - which can be ordered at the aspect level.

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • Spring AOP 切面@Around注解的用法说明

    @Around注解可以用来在调用一个具体方法前和调用后来完成一些具体的任务. 比如我们想在执行controller中方法前打印出请求参数,并在方法执行结束后来打印出响应值,这个时候,我们就可以借助于@Around注解来实现: 再比如我们想在执行方法时动态修改参数值等 类似功能的注解还有@Before等等,用到了Spring AOP切面思想,Spring AOP常用于拦截器.事务.日志.权限验证等方面. 完整演示代码如下: 需要说明的是,在以下例子中,我们即可以只用@Around注解,并设置条件,

  • 解决@Around对静态方法不生效的问题

    场景: 在处理定时任务时,由于这几个方法都是静态方法,在aop的切面中使用@Around注解,进行监控方法调用是否有异常. 发现aop没有生效. 代码如下: /*切面类*/ @Aspect @Component public class RetryAop { private static Logger logger = LoggerFactory.getLogger(RetryAop.class); @Around(value = "@annotation(TechlogRetry)"

  • 使用AOP的@Around后无返回值的解决

    经测试,是环绕通知改变了返回值,切面方法需要有返回值,来代替被代理方法返回结果 改成如下即可: @Around("point_update()") public Object update(ProceedingJoinPoint jp) throws Throwable{ ... Object result = jp.proceed(); ... return result; } 让其执行后的结果返回即可. 补充:spring aop @Around 返回参数值为空 在做 spring

  • 聊聊Spring AOP @Before @Around @After等advice的执行顺序

    用过spring框架进行开发的人,多多少少会使用过它的AOP功能,都知道有@Before.@Around和@After等advice. 最近,为了实现项目中的输出日志和权限控制这两个需求,我也使用到了AOP功能. 我使用到了@Before.@Around这两个advice.但在,使用过程中,却对它们的执行顺序并不清楚. 为了弄清楚在不同情况下,这些advice到底是以怎么样的一个顺序进行执行的,我作了个测试,在此将其记录下来,以供以后查看. 前提 对于AOP相关类(aspect.pointcut

  • Spring Cloud Gateway 默认的filter功能和执行顺序介绍

    目录 Spring Cloud Gateway 默认的filter功能和执行顺序 有效性 调试方法 filters(按执行顺序) spring cloud gateway之filter实战 1.filter的作用和生命周期 2.AddRequestHeader GatewayFilter Factory Spring Cloud Gateway 默认的filter功能和执行顺序 有效性 Spring Cloud Gateway 2.0.0.RELEASE 调试方法 新建一个GlobalFilte

  • 聊聊Spring——AOP详解(AOP概览)

    目录 一.对AOP的初印象 首先先给出一段比较专业的术语: 然后我们举一个比较容易理解的例子: 二.AOP中的相关概念 这里还是先给出一个比较专业的概念定义: 然后举一个容易理解的例子: 三.其他的一些内容 Advice 的类型 一.对AOP的初印象 首先先给出一段比较专业的术语: 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP是OOP的延续,是软件开发中的一个热点,也是

  • 基于spring AOP @Around @Before @After的区别说明

    此段小代码演示了spring aop中@Around @Before @After三个注解的区别 @Before是在所拦截方法执行之前执行一段逻辑. @After 是在所拦截方法执行之后执行一段逻辑. @Around是可以同时在所拦截方法的前后执行一段逻辑. 连接点(JoinPoint) 这个就更好解释了,就是spring允许你是通知(Advice)的地方,那可就真多了,基本每个方法的前.后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点. 其他如AspectJ还可以

  • 浅谈spring aop的五种通知类型

    spring aop通知(advice)分成五类:  前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常. 正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行. 异常返回通知[After throwing advice]:在连接点抛出异常后执行. 返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回

  • 详细解读Java Spring AOP

    一.对AOP的初印象 首先先给出一段比较专业的术语(来自百度): 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. 然后我们举一个比较容易理解的例

  • Spring AOP源码深入分析

    目录 1. 前言 2. 术语 3. 示例 4. @EnableAspectJAutoProxy 5. AbstractAutoProxyCreator 6. 构建Advisor 7. 创建代理对象 8. DynamicAdvisedInterceptor 9. CglibMethodInvocation 10. Advice子类 1. 前言 Spring除了IOC和DI,还有另一个杀手锏功能——Spring AOP.AOP是一种面向切面的编程思想,它的关注点是横向的,不同于OOP的纵向.面向对象

  • Spring AOP执行先后顺序实例详解

    这篇文章主要介绍了Spring AOP执行先后顺序实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 众所周知,spring声明式事务是基于AOP实现的,那么,如果我们在同一个方法自定义多个AOP,我们如何指定他们的执行顺序呢? 网上很多答案都是指定order,order越小越是最先执行,这种也不能算是错,但有些片面. 配置AOP执行顺序的三种方式: 通过实现org.springframework.core.Ordered接口 @Compo

  • Spring注解配置AOP导致通知执行顺序紊乱解决方案

    今天在测试Spring的AOP时,发现使用注解配置AOP的方式会导致通知的执行顺序紊乱.[最终通知居然在异常通知之前执行了] 测试代码 (1)定义TargetInterface目标接口 public interface TargetInterface { public abstract void targetProxy(); } (2)定义TargetImpl目标类 @Component("target") public class TargetImpl implements Targ

  • 图解JAVA中Spring Aop作用

    假如没有aop,在做日志处理的时候,我们会在每个方法中添加日志处理,比如 但大多数的日子处理代码是相同的,为了实现代码复用,我们可能把日志处理抽离成一个新的方法.但是这样我们仍然必须手动插入这些方法. 但这样两个方法就是强耦合的,假如此时我们不需要这个功能了,或者想换成其他功能,那么就必须一个个修改. 通过动态代理,可以在指定位置执行对应流程.这样就可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置 插入这些功能.这样的思想,被称为面向切面编程,亦即AOP. 为了在指定位置执行这些横

随机推荐