今天被人问了个问题,说SpringAop里面报错,@Transactional事务会进行回滚吗? 当时第一个反应是不会,想法是bean对象实际是获取的一个proxy,@Transactional不会比我们的aop更后面执行吧?但是也不能确定,所以就有了今天这篇文章。
我们先来看看aop里面的各个方法的执行顺序
/**
* @Author:TangFenQi
* @Date:2021/11/4 23:59
**/
@Aspect
@Component
@Order(1)
public class ExceptionAop {
/**
* 定义切入点:对要拦截的方法进行定义与限制,如包、类
*
* 1、execution(public * *(..)) 任意的公共方法
* 2、execution(* set*(..)) 以set开头的所有的方法
* 3、execution(* per.spring.boot.learning.q1.service.MyService.*(..)),MyService这个类里的所有的方法
* 4、execution(* per.spring.boot.learning.q1.service.*.*(..)),service包下的所有的类的所有的方法
* 5、execution(* per.spring.boot.learning.q1.service..*.*(..)),service包及子包下所有的类的所有的方法
* 6、execution(* per.spring.boot.learning.q1.service..*.*(String,?,Long)) service包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法
*/
@Pointcut("execution(* per.spring.boot.learning.q1.service.MyService.*(..))")
private void pointCut(){
}
/**
* 方法之前
*/
@Before("pointCut()")
private void before(JoinPoint joinPoint){
System.out.println("before");
}
/**
* 方法执行完毕之后
*/
@AfterReturning(returning = "returnObject",pointcut = "pointCut()")
private void afterReturning(JoinPoint joinPoint,Object returnObject){
//throw new RuntimeException("after running 报错");
System.out.println("after return");
}
/**
* 方法之后
*/
@After("pointCut()")
public void after(JoinPoint joinPoint){
//throw new RuntimeException("after 报错");
System.out.println("after");
}
/**
* 方法抛出异常执行
*/
@AfterThrowing(pointcut = "pointCut()",throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex){
System.out.println("throwing");
}
/**
* 环绕执行,执行之前,执行之后
*/
@Around("pointCut()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around-before");
proceedingJoinPoint.proceed();
System.out.println("around-after");
}
}
输出的执行顺序:
around-before
before
===============
业务方法
===============
after return
after
around-after
这里看似是两个问题,实际上问的是一个问题,为什么这么说呢?
这其实都是在动态代理下的不同实现而已,本质上也就是不同的Interceptor,我们结合一个图来看看.
所以就像上所描述的一样,本质我们搞清楚了,那么两者之间的关系就是平级关系。
而我们将Bean托管给Spring的容器管理,我们从中获取的对象,其实是一个增强类,或者说是代理类。
这里我们的Service是MyService,实际拿到的是spring通过cglib动态生成的MyServiceProxy,当我们访问方法时,就会进过下面的一系列Interceptor(0-6),其中我们要看到就是第一个DynamicAdvisedInterceptor,类如其名,动态通知拦截器里面就有我们的主角。
进入DynamicAdvisedInterceptor类的intercept方法看下(只需要注意中文注释的地方),
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
//只需要注意这里
//这里就是获取到所有的Advice,也就是aop 里面定义的方法或者 transactional的实现。
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
//这里就是将上面获得的所有Advice集合(chain变量)放入其中,并开始访问
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
下图是我一些advice
而这里面第一个就是TransactionInterceptor帮助我们提交或回滚事务的Interceptor,
而我自己定义的Aop切面类,在他的后面。
在运行process()方法(同样只需要注意中文注释的地方)
@Override
@Nullable
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
//interceptorsAndDynamicMethodMatchers就是我们的chain集合
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
//按照索引从0到1逐渐取出拦截器执行
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
从中可以得知,执行顺序就是chain集合的放入顺序。
所以按照上面的理解,执行顺序就是
这里有个小细节啊,可能有些同学发现,不是说after在afterReturning之后执行吗?在chain中怎么还在前面啊,这是应为after是后置方法啊,如果前置方法当然是排在前面的先执行,而对于后置方法反而是先执行的后运行(因为有process()方法),这可能有点绕,需要同学稍微理解一下。
所以从这个源码中也与我们实际运行的结果相互印证,那么问个问题,如果在afterReturning中报错抛出异常,那么afterThrowing是否能捕获?
按照上述理解的原理,是不可能捕获的到的,因为AfterThrowing在chain的最后,反而是最先执行完毕的,所以他对于上层是无法感知的。
下图(只是描述后置方法的情况,前置就是按顺序嘛,好理解,后置需要反过来)就是类的包裹情况(理解为wrapper好点),下层是无法感知到上层的情况的
回到最开始的问题,SpringAop抛出异常是否会导致@Transactional注解失效?其实不会…当然本质不是因为不会,而是我们默认的加载情况下TransactionInterceptor会比我们的Aop先加载进行,如果我们能调整他的顺序,那么StringAop切面当然会影响到@Transactional注解啊。
那么我们怎么去调整顺序呢?
通过spring的 @Order注解,越小越先执行
@Aspect
@Component
@Order(1)
public class ExceptionAop {
...
}
然后在看下chain的排序
方法访问的流程
其中chains就是一个扩展点,他的不同实现会提供不同的功能,比如说日志记录,事务回滚等一些非业务的通用代码,理解了这个对我们实际应用spring会有很大的帮助。
到此,本章结束。
代码无涯,与君共勉。
项目地址:github
喜欢的同学,不介意的话给我点个star吧 (* ̄︶ ̄)