关于spring:BeforeAround和After执行顺序

4次阅读

共计 7728 个字符,预计需要花费 20 分钟才能阅读完成。

用过 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/aop…

咱们会看到输入的后果是:

[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/aop…

咱们会看到输入的后果是:

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

论断
在一个办法只被一个 aspect 类拦挡时,aspect 类外部的 advice 将依照以下的程序进行执行:

失常状况:
one-ok

异常情况:
one-exception

状况二: 同一个办法被多个 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/aop…

咱们会看到输入的后果是:

[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/aop…

咱们会看到输入的后果是:

[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 先执行。如下图所示:
two-ok

留神点
如果在同一个 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/…
其中,AOP 的执行程序章节为:http://docs.spring.io/spring/…
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.

        <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-e44c3c0e64.css" rel="stylesheet">
            </div>
正文完
 0