Spring AOP之坑:完全搞清楚advice的执行顺序
原文地址 blog.csdn.net
目录#
文章目录#
- 目录
- AOP 的核心概念
- 模拟 aspect advice 的执行过程
- 同一 aspect,不同 advice 的执行顺序
- 不同 aspect,advice 的执行顺序
- 同一 aspect,相同 advice 的执行顺序
- Transactional Aspect 的优先级
AOP 的核心概念#
要完全理解 Spring AOP 首先要理解 AOP 的核心概念和术语,这些术语并不是 Spring 指定的,而且很不幸,这些术语并不能直观理解,但是,如果 Spring 使用自己的术语,那将更加令人困惑。
- Aspect:切面,由一系列切点、增强和引入组成的模块对象,可定义优先级,从而影响增强和引入的执行顺序。事务管理(Transaction management)在 java 企业应用中就是一个很好的切面样例。
- Join point:接入点,程序执行期的一个点,例如方法执行、类初始化、异常处理。 在 Spring AOP 中,接入点始终表示方法执行。
- Advice:增强,切面在特定接入点的执行动作,包括 “around,” “before” and "after" 等多种类型。包含 Spring 在内的许多 AOP 框架,通常会使用拦截器来实现增强,围绕着接入点维护着一个拦截器链。
- Pointcut:切点,用来匹配特定接入点的谓词(表达式),增强将会与切点表达式产生关联,并运行在任何切点匹配到的接入点上。通过切点表达式匹配接入点是 AOP 的核心,Spring 默认使用 AspectJ 的切点表达式。
- Introduction:引入,为某个 type 声明额外的方法和字段。Spring AOP 允许你引入任何接口以及它的默认实现到被增强对象上。
- Target object:目标对象,被一个或多个切面增强的对象。也叫作被增强对象。既然 Spring AOP 使用运行时代理(runtime proxies),那么目标对象就总是代理对象。
- AOP proxy:AOP 代理,为了实现切面功能一个对象会被 AOP 框架创建出来。在 Spring 框架中 AOP 代理的默认方式是:有接口,就使用基于接口的 JDK 动态代理,否则使用基于类的 CGLIB 动态代理。但是我们可以通过设置
proxy-target-class="true"
,完全使用 CGLIB 动态代理。 - Weaving:织入,将一个或多个切面与类或对象链接在一起创建一个被增强对象。织入能发生在编译时 (compile time )(使用 AspectJ 编译器),加载时(load time),或运行时(runtime) 。Spring AOP 默认就是运行时织入,可以通过
枚举AdviceMode
来设置。
模拟 aspect advice 的执行过程#
在这里我们不再展示测试代码,而是通过简单的代码来模拟 aspect advice 的执行过程。
尽管 Spring AOP 是通过动态代理
来实现的,但是我们可以绕过代理,直接模拟出它的执行过程,示例代码:
package doubt;
public class AspectAdviceInvokeProcess {
public static void main(String[] args){
try {
//正常执行
AspectInvokeProcess(false);
System.out.println("=====分割线=====");
//异常执行
AspectInvokeProcess(true);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 切面执行过程
* @param isException
* @throws Exception
*/
public static void AspectInvokeProcess(boolean isException) throws Exception{
try {
try {
aroundAdvice(isException);
} finally {
afterAdvice();
}
afterReturningAdvice();
return;
} catch (Exception e) {
afterThrowingAdvice(e);
throw e;
return;
}
}
/**
* 环绕增强
* @param isException
* @throws Exception
*/
private static void aroundAdvice(boolean isException) throws Exception {
System.out.println("around before advice");
try {
JoinPoint_Proceed(isException);
} finally {
System.out.println("around after advice");
}
}
/**
* 编织后的接入点执行过程
* @param isException
*/
public static void JoinPoint_Proceed(boolean isException){
beforeAdvice();
targetMethod(isException);
}
/**
* 前置增强
*/
private static void beforeAdvice() {
System.out.println("before advice");
}
/**
* 目标方法
* @param isException
*/
private static void targetMethod(boolean isException) {
System.out.println("target method 执行");
if(isException)
throw new RuntimeException("异常发生");
}
/**
* 后置增强
*/
private static void afterAdvice() {
System.out.println("after advice");
}
/**
* 正常返回增强
*/
private static void afterReturningAdvice() {
System.out.println("afterReturning");
}
/**
* 异常返回增强
* @param e
* @throws Exception
*/
private static void afterThrowingAdvice(Exception e) throws Exception {
System.out.println("afterThrowing:"+e.getMessage());
}
}
同一 aspect,不同 advice 的执行顺序#
上述代码的执行结果,直接体现了同一apsect中不同advice的
执行顺序,结果如下:
around before advice
before advice
target method 执行
around after advice
after advice
afterReturning
===============分割线==============
around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生
不同 aspect,advice 的执行顺序#
详情可见,《Spring 官方文档》
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-ataspectj-advice-ordering
Spring AOP 通过指定aspect
的优先级,来控制不同aspect,advice的执行顺序
,有两种方式:
-
Aspect 类添加注解:org.springframework.core.annotation.Order,使用注解
value
属性指定优先级。 -
Aspect 类实现接口:org.springframework.core.Ordered,实现 Ordered 接口的 getOrder() 方法。
其中,数值越低,表明优先级越高,@Order 默认为最低优先级,即最大数值:
/**
* Useful constant for the lowest precedence value.
* @see java.lang.Integer#MAX_VALUE
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
最终,不同aspect,advice的执行顺序
:
- 入操作(Around(接入点执行前)、Before),优先级越高,越先执行;
- 一个切面的入操作执行完,才轮到下一切面,所有切面入操作执行完,才开始执行接入点;
- 出操作(Around(接入点执行后)、After、AfterReturning、AfterThrowing),优先级越低,越先执行。
- 一个切面的出操作执行完,才轮到下一切面,直到返回到调用点;
先入后出,后入先出
同一 aspect,相同 advice 的执行顺序#
同一aspect,相同advice的执行顺序
并不能直接确定,而且 @Order 在advice
方法上也无效,但是有如下两种变通方式:
- 将两个 advice 合并为一个 advice,那么执行顺序就可以通过代码控制了
- 将两个 advice 分别抽离到各自的 aspect 内,然后为 aspect 指定执行顺序
Transactional Aspect 的优先级#
Spring 事务管理(Transaction Management),也是基于 Spring AOP。
在 Spring AOP 的使用中,有时我们必须明确自定义 aspect 的优先级低于或高于事务切面(Transaction Aspect),所以我们需要知道:
- 事务切面优先级:默认为最低优先级
LOWEST_PRECEDENCE = Integer.MAX_VALUE
- 事务的增强类型:Around advice,其实不难理解,进入方法开启事务,退出方法提交或回滚,所以需要环绕增强。
public abstract aspect AbstractTransactionAspect extends TransactionAspectSupport implements DisposableBean {
protected AbstractTransactionAspect(TransactionAttributeSource tas) {
setTransactionAttributeSource(tas);
}
@SuppressAjWarnings("adviceDidNotMatch")
Object around(final Object txObject): transactionalMethodExecution(txObject) {
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
try {
return invokeWithinTransaction(methodSignature.getMethod(), txObject.getClass(), new InvocationCallback() {
public Object proceedWithInvocation() throws Throwable {
return proceed(txObject);
}
});
}
catch (RuntimeException ex) {
throw ex;
}
catch (Error err) {
throw err;
}
catch (Throwable thr) {
Rethrower.rethrow(thr);
throw new IllegalStateException("Should never get here", thr);
}
}
}
- 如何修改事务切面的优先级:
在开启事务时,通过设置@EnableTransactionManagement
和<tx:annotation-driven/>
中的,order
属性来修改事务切面的优先级。
详情可见,《Spring 官方文档》https://docs.spring.io/spring/docs/4.3.18.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#tx-annotation-driven-settings
作者: "无问西东"
出处:https://www.cnblogs.com/csyh/articles/13246574.html
版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步