上篇博客解析了事务生成动态代理对象的底层代码,简单总结的话就是一句话:如果添加了事务注解@Transactional,且方法是public的,spring就会给该bean生成代理对象,至于是jdk还是cglib,就看自己的事务方法对应的类是否实现了接口
接下来我们来看生成代理对象之后的执行过程
拦截器执行
org.springframework.transaction.interceptor.TransactionInterceptor
我在学习的时候,用的是cglib代理,因为我没有实现接口,所以使用的是CGLIB代理,在目标方法被调用的的时候,是会调用到
org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept; 然后会调用到
org.springframework.transaction.interceptor.TransactionInterceptor#invoke
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
这个方法我删减了一部分,区分了声明式事务和编程式事务,这里我们只关注声明式事务
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
//获取事务属性
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//获取事务管理器
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
//下面是声明式事务的处理逻辑
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
//看是否有必要创建一个事务,根据事务的传播行为做判断
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
//执行目标方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
//如果有异常就回滚事务,回滚时,根据配置的rollbackFor进行判断
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//清除事务信息
cleanupTransactionInfo(txInfo);
}
//提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
这个方法中,
- 根据事务注解对应的传播机制,判断是否要创建新的事务,还是嵌套在当前事务中运行等;
- 接着执行目标方法
- 在执行完目标方法之后,如果未发生异常,则提交事务
- 如果发生异常,根据配置的rollbackFor和noRollbackFor等判断是否要回滚,如果无需回滚,就提交事务,反之,则回滚事务
因此,我们着重关注两个方法
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
和
completeTransactionAfterThrowing(txInfo, ex);
传播机制源码
传播机制的源码我整理之后再补充
异常回滚源码
如果在执行目标方法的时候,如果发生异常,捕获到之后,会进入到这个方法来处理
/**
* 这里是判断是否需要回滚的逻辑
* 如果在事务注解上指定了回滚的异常类型、或者指定了不回滚的异常类型,就会在这里进行判断
* 1.如果判断满足回滚的条件,就事务回滚
* 先判断开发人员指定的类型,如果业务代码抛出的异常符合指定的类型,就回滚
* 如果没有指定,就判断是否是错误(error)或者运行时异常(runTimeException),如果是,就回滚
* 所以:如果程序员没有指定回滚的异常,默认情况下,如果是运行时异常或者是error(错误),也是会进行事务的回滚
* 2.否则,就提交事务
* @param txInfo
* @param ex
*/
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
/**
* 如果程序员有指定回滚或者不回滚的异常,就会进入
* org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn(java.lang.Throwable)进行判断
*
* 如果没有指定,就默认调用org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(java.lang.Throwable)
* 进行判断
*/
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// 这里else就是不符合回滚的条件,会进行事务的提交(即使发生了异常场景)
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
这里比较重要的一个方法,就是进行异常的判断,判断当前业务代码抛出的异常,是否符合回滚的要求;
这里这个方法就是来判断当前抛出的异常是否符合在@Transactional注解上配置的异常回滚信息
org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn
@Override
public boolean rollbackOn(Throwable ex) {
if (logger.isTraceEnabled()) {
logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
}
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
// 这里的rollbackRules是在解析@Transactional注解的时候,保存的;parseTransactionAnnotation在这个方法中解析的注解;这里的rollbackRules既有rollbackException中配置的,也有noRollbackException中配置的
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
/**
* depth:当前rule和ex的相似度
*
* deepest:和ex最相近的depth
* winner:相似度最近的RollbackRuleAttribute
* 如果当前rule,返回的depth比上一次返回的depth小,且大于0,就用当前这次异常
*
* 这里会保存和业务代码抛出异常最相似的rule
* 比如:
* 我在代码中,抛出了一个java.lang.ArithmeticException: / by zero
*
* 如果我在@Transactional(rollbackFor = {Exception.class,ArithmeticException.class})
* 那肯定会返回ArithmeticException这个rule,因为ArithmeticException返回的depth是0
* Exception返回的的depth是2 下面解释为什么一个是0,一个是2
*/
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Winning rollback rule is: " + winner);
}
// User superclass behavior (rollback on unchecked) if no rule matches.
// 如果没有匹配到最相似的异常、或者没有配置回滚异常类,就会执行这里,这里是调用父类的方法进行判断
if (winner == null) {
logger.trace("No relevant rollback rule found: applying default rules");
return super.rollbackOn(ex);
}
/**
* 如果相似度最近的rule不是无需回滚的类型,就可以进行事务回滚
*/
return !(winner instanceof NoRollbackRuleAttribute);
}
// 这里就是父类对应的判断逻辑,也就是说:如果程序员单单加了一个@Transactional注解,那么在业务代码抛出运行时异常后者error的时候,还是会回滚的
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
那我们接着来看上面判断异常相似度的方法:int depth = rule.getDepth(ex);
这里我称之为相似度,不知道是否合适;这里是根据业务代码中抛出的异常A对应的name和程序员指定的异常B对应的name进行比较,如果匹配上,就返回depth,如果匹配不上,就递归调用,用业务代码中抛出的A异常对应的父类和B异常的name进行比较,依次递归调用
/**
* 这里其实就是用业务代码抛出的异常和程序员在@Transactional注解中执行的异常信息进行对比
*
* 实际底层使用的是String.contains()方法
* exceptionName:就是程序员指定的异常对应的类名;比如:我指定的是rollbackFor = Exception.class,那这里的exceptionName就是:java.lang.Exception
*
* 1.如果当前抛出的异常和程序员指定的异常匹配不上,就依次递归调用抛出异常的父类和程序员指定的异常进行比较,
* 1.1 直到匹配上,就返回当前的depth,depth每递归调用一次,就+1
* 1.2 或者是到Throwable依旧没有比对上,这时,就表示我指定的异常和代码抛出的异常不匹配
*
* 这两种场景也好验证:
* 1.首先,我在业务代码中,加上这么一行代码:int i = 10/0;
* 2.然后在@Transactional注解中加上rollbackException = Exception.class 或者是rollbackException = IoException.class
* 这两种异常,最后事务都会回滚,但是效果却是不一样的
* 如果我加的是rollbackException = Exception.class,这里会匹配上,返回的是depth是2
* 但是如果加的是rollbackException = IoException.class,这里返回的是-1
*
* 因为:如果是rollbackException = Exception.class;那这里在匹配的时候,会递归调用两次,
* int i = 10/0;会抛出java.lang.ArithmeticException: / by zero
* ArithmeticException的父类是RuntimeException;RuntimeException的父类是Exception;所以只有递归调用两次,才能匹配到我指定的Exception.class
*
* 但是,如果我指定的是IoException.class,那永远也匹配不上,因为IOException和ArithmeticException都继承了RuntimeException,是并行的关系,在最后
* 递归调用到父类Throwable的时候,就会返回-1(即使这里返回了-1,最后事务还是会回滚,为什么?因为在方法之后会判断,如果程序员指定的异常和当前业务代码抛出的异常不相似,那就会判断业务代码抛出的异常是否是运行时异常或者error)
*
* @param exceptionClass:当前业务代码抛出的异常/或者是抛出异常对应的父类
* @param depth:相似度/或者说是深度
* @return
*/
private int getDepth(Class<?> exceptionClass, int depth) {
if (exceptionClass.getName().contains(this.exceptionName)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass == Throwable.class) {
return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}
所以,在业务代码抛出异常的时候,
- 会判断异常的类型和程序员指定的异常类型,会找到匹配度最为相似的异常,最后再判断最相似的异常 是否是noRollbackException配置的异常类,如果是,就无需回滚,提交事务即可
- 如果没有匹配上,就判断抛出的异常是否是Error或者RuntimeException,如果是,也会回滚