起因
之前测试反馈过一个问题,钱没扣成功,但是却生成了扣钱的记录,最终解决了,但是没有去深究。
目标
- 了解Spring Boot Transaction的核心技术点
- 了解事务相关的钩子,方便后续日常开发
- 了解一下多数据源的实现原理
准备工作
在https://start.spring.io/ 或者基于IDEA
快速生成一个MVC
的项目,便于调试,包括Web
, Mybatis
, Test
等
前言
1、本次探究基于Spring Boot 5.0.2
,其次主要看基于@Transactional
的声明式的事务。
2、Spring Transaction
的实现方案为Spring AOP
,如果对于Spring AOP
不是很了解,建议多花一些时间研究一下
3、基于Spring Boot自动配置原理,核心在于查找xxxAutoConfiguration
自动配置类 --> TransactionAutoConfiguration
在阅读源码前可以先思考,猜测
Spring
将会如何利用AOP
理念优雅的实现事务,比如:
1、必须有一个类,或者一些实现类,来共同完成判断是否要给方法增加事务,事务属性等等的工作
2、基于Spring Boot
默认使用CGLIB
,那么一个有实现MethodInterceptor
的事务拦截器
3、猜测是基于ThreadLocal
实现的默认的事务传播属性
4、……
核心源码分析
翻阅刚刚说的TransactionAutoConfiguration
-> EnableTransactionManagement
-> TransactionManagementConfigurationSelector
-> AutoProxyRegistrar
, ProxyTransactionManagementConfiguration
这两个类非常核心
AutoProxyRegistrar
该类做了的事非常核心,翻阅源码,重点关注
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
-> registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
创建了InfrastructureAdvisorAutoProxyCreator
,紧见其名,便可猜测它的核心能力,与创建动态代理对象相关
其次关注它的父类,因为在对象实例到Spring容器
的整个过程会有很多钩子函数,比如很常见的BeanPostProcessor
,父类实现了该接口,看AbstractAutoProxyCreator
,它的postProcessAfterInitialization
的核心能力在于,当Bean
被放入容器初始化完成后进行一些处理,比如是否需要生成代理对象进行包装
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
到这里,很明确,所有的对象都会经过该层进行处理,但是前提是需要对象是放在
Spring
容器的对象,如果不是,则不会被处理。所以一般只有诸如@Service
,@Compent
等等的方式才会被处理,记住wrapIfNecessary
方法,稍后回来继续分析
ProxyTransactionManagementConfiguration
此类非常核心,创建的几个Bean
都是实现Spring Transaction
最核心的部分内容
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
// 事务属性增强器
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource,
TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.getNumber("order"));
}
return advisor;
}
// 事务属性
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
// 事务拦截器
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(
TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
BeanFactoryTransactionAttributeSourceAdvisor
事务增加器,请关注它的父类,其次关注它的属性,构造的时候包括一个AnnotationTransactionAttributeSource
,以及一个TransactionInterceptor
它实现了一个PointcutAdvisor
接口,而最终该接口返回的对象是TransactionAttributeSourcePointcut
,从接口名称可以得出该方法与AOP
的切面相关,其次它也实现了接口MethodMatcher
,匹配规则,入参是方法,以及类对象,而它的实现如下
@Override
public boolean matches(Method method, Class> targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
注意tas
其实是AnnotationTransactionAttributeSource
,而该类扩展了AbstractFallbackTransactionAttributeSource
,而该父类的getTransactionAttribute
方法包括了
@Override
@Nullable
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class> targetClass) {
// ....
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
}
往下继续看会翻到的代码如下:
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
for (TransactionAnnotationParser parser : this.annotationParsers) {
TransactionAttribute attr = parser.parseTransactionAnnotation(element);
if (attr != null) {
return attr;
}
}
return null;
}
// annotationParsers在对象初始化时默认如下
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
// SpringTransactionAnnotationParser 的核心
@Override
@Nullable
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
element, Transactional.class, false, false);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
至此,终于看到熟悉的@Transactional
,也就是说,在这里对方法上的注解进行分析,拆分成一个事务属性对象,至此可以看到BeanFactoryTransactionAttributeSourceAdvisor
该类的核心能力就在于将对象的方法上那些写了@Transactional
给找出来并进行分析,而且该对象还内置了TransactionInterceptor
,以此相互串联起来,如果这些对象需要增强,那么就通过动态代理技术生成一个新的代理对象。那么什么时候开始找@Transactional
,以此确定TransactionInterceptor
,始于前文提到的wrapIfNecessary
方法
AbstractAutoProxyCreator
wrapIfNecessary 的核心在于判断当前对象需要进行相应的增强方式,并找到它们所对应的拦截器
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
getAdvicesAndAdvisorsForBean
-> findEligibleAdvisors
-> findAdvisorsThatCanApply
-> AopUtils.findAdvisorsThatCanApply
-> canApply
public static boolean canApply(Advisor advisor, Class> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) { // BeanFactoryTransactionAttributeSourceAdvisor 扩展该接口
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
public static boolean canApply(Pointcut pc, Class> targetClass, boolean hasIntroductions) {
// ....
MethodMatcher methodMatcher = pc.getMethodMatcher();
// ....
for (Class> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) { // 查阅 TransactionAttributeSourcePointcut
return true;
}
}
}
return false;
}
查阅 TransactionAttributeSourcePointcut
@Override
public boolean matches(Method method, Class> targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
至此,终于跟前文提到的解析过程合理的相关联起来,处理的最核心在于使用BeanPostProcessor
,而要注意的是在查找过程最终返回的是Advisor
,而我们都知道CGLIB
创建的时候是MethodInterceptor
,但是,MethodInterceptor
却是扩展自Advisor
。
所以取到Advisor
对象列表,就可能是实现了MethodInterceptor
的对象列表,翻阅生成代理对象时也确实会判断
@Override
public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
// ....
Advice advice = (Advice) adviceObject;
if (advice instanceof MethodInterceptor) {
// So well-known it doesn't even need an adapter.
return new DefaultPointcutAdvisor(advice);// 将对象进行包装
}
// ...
}
至此,从事务方法是如何被一步步扫描,解析,并通过
CGLIB
生成代理对象来实现切面事务
的方法的完整过程算是大致清晰了,重点关注的核心对象的创建类ProxyTransactionManagementConfiguration
TransactionInterceptor
事务拦截器的真正处理的地方。
而在了解事务处理机制之前,先梳理一下已有的知识点,包括
- 在配置文件中配置
spring.datasource.xxx
等信息 -
JDBC
原生的事务接口,包括对象DataSource
,Connection
等等 - 在XML配置文件时代常见
DataSourceTransactionManager
切面配置(事务管理器) - ...
本次翻阅源码主要想要了解的点,如下:
-
Spring Boot
在事务上的一些配置类 - 事务提交/回滚等过程涉及的一些核心异常
- 事务提交/回滚等过程提供的一些钩子函数
- 多数据源的设计方案原理
- ....
事务切面的核心程序如下:
@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);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
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;
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;
}
// ...
}
从spring.datasource.xxx
很容易找到DataSourceProperties
, 而通过Debug
也很容易发现事务管理器的对象是DataSourceTransactionManager
, 而二者的核心配置类是DataSourceTransactionManagerAutoConfiguration
而Hikari
也是作为Spring Boot
新默认的一个数据源解决方案。
从createTransactionIfNecessary
入口看事务的创建过程
createTransactionIfNecessary
-> tm.getTransaction(txAttr)
-> doGetTransaction()
-> conHolder = TransactionSynchronizationManager.getResource(obtainDataSource())
最后一步是非常核心的,获取数据源,并从资源池中获取连接
obtainDataSource()
的源码没什么秘密,无非就是一个返回一个数据源对象,那么多数据源要如何设计?看看Spring
提供的多数据源方案AbstractRoutingDataSource
,改接口是2.0.1版本新加入的,而且可以看到其实接口AbstractRoutingDataSource
也并没有多少秘密,那么多数据源如何实现?
不能改变Spring Transaction
原来的程序,那么切换数据源的程序该放在哪里呢?答案是放在数据源对象本身,因为只需要确保,每次需要获取一个数据库连接来完成操作时,从某一个返回的数据源
中获取一个即可,所以AbstractRoutingDataSource
的方案是提供数据源切换的一种方案
TransactionSynchronizationManager.getResource
的源码似乎也无甚秘密可言,一步步跟进去发现,最终的解决方案是
ThreadLocal
, 该线程副本的内部数据结构Map
,其中Key
是数据源,Value
是该数据源的一个连接,而这也正解决了一个请求包含多个数据源的处理思路,但是一个请求多个事务需要提交的时候会比较复杂。
会看到如果是第一次来查找资源,会找不到,所以接下来往下看,源码DataSourceTransactionManager
, 看该类的几个核心方法-
-
doBegin()
, 从数据源中取出连接并放入缓存,将连接设置为非默认提交,设置超时时间等等,并将该数据源与连接放到线程副本中 doRollback()
doCommit()
rollback的前提是异常条件为:txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)
, 否则则进行commit操作,而这部分异常在于@Transactional#rollbackFor
,而默认逻辑是 (ex instanceof RuntimeException || ex instanceof Error);
这部分代码写的都非常清晰,没有人任何难懂的操作,其次翻阅AbstractPlatformTransactionManager
,看该类的几个核心方法
triggerAfterCommit
triggerAfterCompletion
triggerBeforeCompletion
triggerBeforeCommit
-
triggerFlush
而这部分方法的逻辑在于接口TransactionSynchronization
,俗称钩子函数,常见用法参考如下:
// 事务管理
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
@Override
public void afterCompletion(int status) {
if(TransactionSynchronization.STATUS_COMMITTED == status) {
// ....
}
}
});
总结
至此,整个Spring Boot Transaction
算是梳理了一遍,至少把自己想要探究的一些点捋出来了,脉络看起来也是非常的清晰。
最核心在于要了解BeanPostProcessor
机制,其次要了解AOP
机制,包括AOP
技术点,Advisor
, MethodInterceptor
等等,其次要还要了解关于JDBC 提供的事务相关的接口,Spring Transaction
并未对此做任何改变,都是基于JDBC规范进行的二次封装与设计。
前文提到出现的问题,在于发现程序进入Service
时的方法没有@Transactional
,而是在其内部调用另外一个方法时采取调用另外一个有@Transactional
的方法,所以很不辛,事务没有生效。原因跟动态代理生成的对象是有关系的,采用如下代码记录生成的动态代理的类文件,通过JD-GUI反编译看代码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/usr/local/test/class");
是否开启事务,在于子类,即代理对象,父类只是普通对象,不具备事务的处理能力。
翻看Spring
的源码经常有种其实就这样的感觉……