转载:http://blog.csdn.net/rainbow702/article/details/52185827css
用过spring框架进行开发的人,多多少少会使用过它的AOP功能,都知道有@Before、@Around和@After等advice。最近,为了实现项目中的输出日志和权限控制这两个需求,我也使用到了AOP功能。我使用到了@Before、@Around这两个advice。但在,使用过程当中,却对它们的执行顺序并不清楚。为了弄清楚在不一样状况下,这些advice究竟是以怎么样的一个顺序进行执行的,我做了个测试,在此将其记录下来,以供之后查看。html
当一个方法只被一个Aspect拦截时,这个Aspect中的不一样advice是按照怎样的顺序进行执行的呢?请看:java
该pointcut用来拦截test包下的全部类中的全部方法。web
package test; import org.aspectj.lang.annotation.Pointcut; public class PointCuts { @Pointcut(value = "within(test.*)") public void aopDemo() { } }
该类中的advice将会用到上面的pointcut,使用方法请看各个advice的value属性。spring
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中只有一个方法,可是它会根据参数值的不一样,会做出不一样的处理:一种是正常返回一个对象,一种是抛出异常(由于咱们要测试@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,回车:ruby
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,回车:app
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类,无论它们的advice使用的是同一个pointcut,仍是不一样的pointcut,都有可能致使同一个方法被多个aspect类拦截。那么,在这种状况下,这多个Aspect类中的advice又是按照怎样的顺序进行执行的呢?请看:
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。可是如今 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 的执行顺序呢?
方法有两种:
无论采用上面的哪一种方法,都是值越小的 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 也未被执行。
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