前面我们对Spring事务框架以及Spring启用事务管理的@EnableTransactionManagement进行了学习,了解了Spring事务管理的底层工作机制。
有关Spring事务管理剩余尚未学习研究的还有事务如何生效、以及事务生效后具体的AOP切入和增强过程。
我们先研究事务生效后具体的AOP切入和增强过程。
基于Cglib实现的AOP增强都是通过拦截器Interceptor实现的,通过前面几篇文章的分析,我们知道Spring事务的拦截器是TransactionInteceptor,我们直接从拦截器的invoke方法入手。
TransactionInteceptor#invoke
看一下TransactionInterceptor的类结构:
AOP Alliance MethodInterceptor for declarative transaction management using the common Spring transaction infrastructure (PlatformTransactionManager/ org.springframework.transaction.ReactiveTransactionManager).
Derives from the TransactionAspectSupport class which contains the integration with Spring's underlying transaction API. TransactionInterceptor simply calls the relevant superclass methods such as invokeWithinTransaction in the correct order.AOP联盟的方法拦截器,用于实现基于Spring事务框架的事务管理功能。
继承自集成了Spring底层事务管理API的TransactionAspectSupport类。
所以通过JavaDoc我们知道TransactionIntecepor是Spring事务管理中的方法拦截器,他有一个父类叫TransactionAspectSupport。
开始分析他的invoke方法。
首先获取到targetClass,targetClass其实就是被@Transactional注解的业务类,通过参数invocation传进来。
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
然后调用invokeWithinTransaction方法,该方法在TransactionAspectSupport类实现,我们稍后分析。
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
}
我们先来简单看一下invokeWithinTransaction的最后一个参数CoroutinesInvocationCallback(),这是拦截器的回调参数,拦截器做方法增强的时候会通过这个回调参数、调用proceedWithInvocation方法从而执行被代理对象的原始方法。
TransactionAspectSupport#invokeWithinTransaction
第一步获取TransactionAttributeSource,通过TransactinAttributeSource获取TransactionAttribute。TransactionAttribute是事务定义对象,TransactionAttributeSource是事务定义对象的持有者,这些概念我们在前面都已经分析过了。
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);
然后通过TransactionAttributeSource获取TransactionAttribute,是通过调用getTransactionAttribute方法获取的,这个过程我们稍后分析。
然后通过TransactionAttribute调用determineTransactionManager获取TransactionManager:
final TransactionManager tm = determineTransactionManager(txAttr);
determianTransactionManager的代码就不贴出了,感兴趣的可以自己去看一下,代码不算太长,大概的逻辑是获取到TransactionManager,并缓存起来,下次直接从缓存获取。
首次是通过如下代码获取的:
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
其实就是从Spring的Ioc容器中获取,至于注入到容器的到底是哪个TransactionManager其实是取决于应用的,应用可以通过配置的方式、注解的方式注入。SpringBoot项目如果引入JDBC的话,TransactionManager就是JDBCTransactionManager。
之后的一大段代码是响应式事务的,跳过。
然后将TransactionManger转换为PlatformTransactionManager(不是响应式事务,那么一定是PlatformTransactionManager)
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
然后就会跑到这个分支里,因为满足or条件的后半部分,会调用createTransactionIfNecessary,这是事务控制的关键一步:开启事务。这部分我们也稍后处理
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
开启事务之后,接下来的我们知道,肯定要回调业务方法了,回调方法前面我们已经说过了,这里就不再啰嗦了。
我们需要特别关注的是回调之后的处理:如果回调抛出异常后的处理,调用了completeTransactionAfterThrowing,之后将异常继续抛出。如果回调业务方法返回正常的话,就调用commitTransactionAfterReturning(txInfo)提交事务。
Object retVal;
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
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//省略部分代码
commitTransactionAfterReturning(txInfo);
return retVal;
}
那我们到现在就累积了有3个问题待分析:
- 事务开启过程。
- 回调正常返回后的事务提交处理。
- 回调发生异常之后的事务回滚处理。
createTransactionIfNecessary
这个是开启事务的方法。
调用事务管理器的getTransaction,在必要的情况下开启事务(根据事务传播机制)。代码特别简单,事务管理器的getTransaction我们再前面也分析过了。
开启事务之后调用了一个方法prepareTransactionInfo,是为了将当前已开启的事务信息与当前线程绑定后存储在当前对象中,具体作用尚未研究但是对我们理解整个Spring事务管理机制影响不大,可以忽略。
//做了些准备工作,省略代码
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
commitTransactionAfterReturning
回调业务方法正常的话,调用此方法完成事务的提交。
这个方法代码很短,思路也特别清晰,直接调用事务管理器的commit方法提交事务。事务管理器的commit方法我们前面的讲Spring事务框架底层逻辑的文章已经做过很详细的分析了。
到这里相当于是我们从应用切入的角度开始分析,与从Spring事务框架底层逻辑的分析完美会师了。
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
completeTransactionAfterThrowing
回调方法返回异常后的回滚处理,这是我们日常项目开发过程中经常会碰到的情况,项目开发、测试过程中代码跑到这个分支的情况可能要比跑到commit分支的情况还要多。毕竟,程序员吗,就是为了编写bug而生的。
上来先做个判断,一般如果启用了事务的话,肯定能跑进来。
if (txInfo != null && txInfo.getTransactionStatus() != null) {
然后再做一个判断,如果启用事务的话,transactionAttribute也不会为空,关键是后面的这个rollbackOn判断我们需要关注一下,如果判断为true的话就进去做事务回滚的处理。
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
DefaultTransactionAttribute的rollbackOn实现很简单,即使判断抛出的异常是不是运行期异常或Error。这就相当于是找到了一个有关Spring事务管理机制默认只针对RuntimeException或Error回滚事务,对其他异常不回滚事务的底层依据。
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
但是,我们可以在事务定义的时候指定(通过@Transactional注解)事务回滚的异常,但是这个实现在哪儿呢?暂时没有找到,存疑。
好了,Spring事务的AOP环绕切入:业务方法调用事前开启事务、方法调用之后根据调用结果提交事务、或者回滚事务的代码层分析就完成了。希望看到这里的你能有所收获。
AbstractFallbackTransactionAttributeSource#getTransactionAttribute
Determine the transaction attribute for this method invocation.
Defaults to the class's transaction attribute if no method attribute is found.
Params:
method – the method for the current invocation (never null)
targetClass – the target class for this invocation (may be null)
Returns:
a TransactionAttribute for this method, or null if the method is not transactional
获取当前方法的Transaction Attrbute,如果方法的Attribute没有定义的话,默认返回当前类的Attribute。
Transaction Attribute会缓存,首先判断缓存中是否有当前类、当前方法的Attribute数据,有则从缓存获取。
缓存不存在的话,调用computeTransactionAttribute获取之后,写入缓存并返回。
computeTransactionAttribute
默认情况下,只有public方法才支持@Transactional注解,不是public方法的话,不做处理。
// Don't allow non-public methods, as configured.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
调用getMostSpecificMethod,根据注释以及该方法的JavaDoc可知,是为了获取标注了@Transactional注解的实体方法,因为注解可能标注在接口上,在此情况下,getMostSpecificMethod可以找到该接口实现类的对应方法。
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
接下来,获取该方法的@Transactional注解并生成Attribute对象
// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
如果方法层的注解没有获取到的话,则获取类上的注解。
// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
如果方法层、类层都没有获取到的话,并且前面处理过程中当前方法是接口方法、已经被处理为实现类的方法的话,再返回来处理原始方法,处理逻辑还是先看方法层、然后再看类层。
如果最终没有获取到的话,返回null,这种情况下,这个方法没有被@Transactinal注解。
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
具体的解析过程会交给注解解析器处理,注解解析器前面的文章简单提到过,也没有做过详细的分析,这里也不做分析了,他的目的就是解析@Trnsactional注解,获取到其中的事务传播机制、回滚条件、事务超时设置等事务定义的相关属性,最终生成TransactionAttribute后返回。
遗留的小问题:
- 通过@Transactional注解修改事务默认的回滚异常类型的生效方式。
- Spring运行过程中事务处理逻辑(AOP切入过程)具体是在哪一个步骤、在哪一段代码中发生的(也就是文章开头说的事务如何生效的问题)。
- 嵌套事务commit的时候为什么只是释放了savepoint、而没有做真正的提交处理(这个问题其实已经有答案了,改天专门补充一片小文章解释一下即可)。
除以上遗留的小问题之外,SPring事务的引入、底层实现机制、业务处理过程中事务切入及生效过程的底层实现原理等涉及到Spring事务控制的核心过程,基本已经完成了代码级的分析。但愿对你能有所帮助。