我们在日常开发的时候经常会用到组合注解,比如:@EnableTransactionManagement + @Transactional、@EnableAsync + @Async、@EnableAspectJAutoProxy + @Aspect。今天我们就来抽丝剥茧,揭开@Transactional注解的神秘面纱
当我们看到类似@Enablexxx这样的注解,一般源码中都会存在@Import注解。@Import注解在Spring的解析阶段有着十分重要的地位,是Spring的一个重要的扩展点。其注入的class一般继承ImportSelector或ImportBeanDefinitionRegistrar接口,作用分别如下
感兴趣的小伙伴可以阅读下方链接对应博文,该博文主要讲解了Spring对@ComponentScan、@Import、@PropertySource、@Bean等注解的解析流程,可以更好的帮助我们理解本篇文章Spring之ConfigurationClassPostProcessor解析流程https://blog.csdn.net/qq_38257958/article/details/134761961?spm=1001.2014.3001.5501
通过上面的源码,我们简单理论分析
@EnableTransactionManagement注解会注入一个类型为TransactionManagementConfigurationSelector的class,该class的父类实现selectImports方法,父类方法又会调用子类的同名方法。根据上文中阐述的@Import注解的作用,此时Spring容器中多了两个BeanDefinition:一个beanClass为AutoProxyRegistrar,另一个beanClass为ProxyTransactionManagementConfiguration
结论1:@EnableTransactionManagement注解会import一个类型为TransactionManagementConfigurationSelector的class,该class实现ImportSelector接口,其接口方法返回[AutoProxyRegistrar,ProxyTransactionManagementConfiguration]类名数组,即Spring容器在后期会存在beanClass为AutoProxyRegistrar和ProxyTransactionManagementConfiguration的两个bean
AutoProxyRegistrar实现ImportBeanDefinitionRegistrar接口,所以会在Spring的解析阶段执行registerBeanDefinitions方法,我们重点关注截图框住的方法,它会注入一个beanClass为InfrastructureAdvisorAutoProxyCreator的BeanDefinition。InfrastructureAdvisorAutoProxyCreator是BeanPostProcessor(后文简称bpp)的子类,它会在普通bean的生命周期对bean进行一些干预,比如当前bpp就会在Spring执行bpp的postProcessAfterInitialization方法的时候会对bean进行动态代理(这个我们后文分析)
结论2:AutoProxyRegistrar会注入一个类型为InfrastructureAdvisorAutoProxyCreator的bean
这个类比较简单,就是一个配置类,利用@Configuration + @Bean的组合,创建了几个bean
结论3:ProxyTransactionManagementConfiguration会注入beanClass为BeanFactoryTransactionAttributeSourceAdvisor、TransactionAttributeSource、TransactionInterceptor的三个bean
@EnableTransactionManagement注解会import一个实现ImportSelector接口的类,import的类会注入两个bean(beanClass分别为AutoProxyRegistrar和ProxyTransactionManagementConfiguration),其中AutoProxyRegistrar会进一步解析,然后注入一个类型为InfrastructureAdvisorAutoProxyCreator的bpp。ProxyTransactionManagementConfiguration是一个配置类,会注入几个bean,协助@EnableTransactionManagement注解完成相关功能
BeanPostProcessor从本质上说,它也是一个bean,不过它优先实例化,然后作用于普通bean。比如我们耳熟能详的属性注入、动态代理,都是BeanPostProcessor在不同阶段对普通bean进行的处理。
InfrastructureAdvisorAutoProxyCreator继承BeanPostProcessor,在spring执行到postProcessAfterInitialization的时候会查找可以作用于当前bean的Advisors,如果存在符合条件的Advisors,则进行动态代理
具体查找过程可以查看下方链接博文。简单来说就是查找普通bean所属的class和方法上有没有@Transactional注解,如果满足条件则进行AOP动态代理。Spring之AOP源码解析(下)https://blog.csdn.net/qq_38257958/article/details/136182213?spm=1001.2014.3001.5502
根据我们设置的参数,有可能进行JDK动态代理也有可能进行cglib动态代理,如果是JDK动态代理我们关注JdkDynamicAopProxy这个类,如果是cglib动态代理我们关注DynamicAdvisedInterceptor这个类。不管是什么动态代理都会有一个field(advised),这个参数存储了可以作用于当前bean的Advisors,每个Advisor都有一个advice对象(MethodInterceptor的父接口),Spring会将这些advice串成一个拦截器链,链式调用各个拦截器的invoke方法,我们画图演示流程
PS:有兴趣的小伙伴可以把我写的几篇关于AOP的文章都阅读一下,可以更好的帮助我们理解这篇博文。
主要关注其invoke方法,invoke方法主要调用了invokeWithinTransaction方法
- protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
- final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
-
- // 上文分析的ProxyTransactionManagementConfiguration注入的三个bean之一(AnnotationTransactionAttributeSource)
- TransactionAttributeSource tas = getTransactionAttributeSource();
- // 1.从类上查找@Transactional注解
- // 2.从方法上查找@Transactional注解
- // 3.将步骤1或2查找到的@Transactional注解进行解析,构建成TransactionAttribute对象(RuleBasedTransactionAttribute)
- final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
-
- // 1.如果txAttr为null或者beanFactory为null,返回注入的TransactionManager
- // 2.如果txAttr.getQualifier不为null(即Transactional注解的value属性值),则从beanFactory获取配置的bean返回
- // 3.如果注入了transactionManagerBeanName则从beanFactory获取bean返回
- // 4.如果没注入TransactionManager,就从beanFactory获取class为PlatformTransactionManager的bean返回
- final TransactionManager tm = determineTransactionManager(txAttr);
-
- // 省略webFlux相关代码
-
- PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
- final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
-
- if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
- // 开启事务(如果需要)
- TransactionAspectSupport.TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
-
- Object retVal;
- try {
- // 执行下一个拦截器的invoke方法或者目标方法
- retVal = invocation.proceedWithInvocation();
- } catch (Throwable ex) {
- // 处理异常
- completeTransactionAfterThrowing(txInfo, ex);
- throw ex;
- } finally {
- // 解除线程和事务的绑定关系
- cleanupTransactionInfo(txInfo);
- }
-
- if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
- // Set rollback-only in case of Vavr failure matching our rollback rules...
- TransactionStatus status = txInfo.getTransactionStatus();
- if (status != null && txAttr != null) {
- retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
- }
- }
- // 提交事务
- commitTransactionAfterReturning(txInfo);
- return retVal;
- }
-
- }
这个类在@Transactional源码中起着重要作用,它不仅管理每个线程的资源和事务同步,也协助完成与mybatis的集成
TransactionAspectSupport#createTransactionIfNecessary
AbstractPlatformTransactionManager#getTransaction
AbstractPlatformTransactionManager#startTransaction
DataSourceTransactionManager#doBegin
- protected void doBegin(Object transaction, TransactionDefinition definition) {
- DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject) transaction;
- Connection con = null;
-
- try {
- // 如果事务还没有获取Connection或者Connection还没标记为与事务同步
- if (!txObject.hasConnectionHolder() ||
- txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
- // 获取Connection
- Connection newCon = obtainDataSource().getConnection();
- if (logger.isDebugEnabled()) {
- logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
- }
- // 将Connection标记为new Connection
- txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
- }
-
- // 将Connection没标记为与事务同步
- txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
- con = txObject.getConnectionHolder().getConnection();
-
- // 设置事务的隔离级别
- Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
- txObject.setPreviousIsolationLevel(previousIsolationLevel);
- txObject.setReadOnly(definition.isReadOnly());
-
- // 将Connection的自动提交关闭
- if (con.getAutoCommit()) {
- txObject.setMustRestoreAutoCommit(true);
- if (logger.isDebugEnabled()) {
- logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
- }
- con.setAutoCommit(false);
- }
-
- prepareTransactionalConnection(con, definition);
- // 将事务标记为已激活
- txObject.getConnectionHolder().setTransactionActive(true);
-
- // 设置过期时间(如果手动设置了)
- int timeout = determineTimeout(definition);
- if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
- txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
- }
-
- // 如果是新连接,事务同步管理器同步资源
- if (txObject.isNewConnectionHolder()) {
- TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
- }
- }
-
- catch (Throwable ex) {
- if (txObject.isNewConnectionHolder()) {
- DataSourceUtils.releaseConnection(con, obtainDataSource());
- txObject.setConnectionHolder(null, false);
- }
- throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
- }
- }
PS : Spring源码中存在很多模板模式,很多方法都是交给子类去实现,比如上文中的doGetTransaction,isExistingTransaction,doBegin等方法,如果我们不确定具体走的是那个子类,可以多多去debug看看。这里TransactionManager主要是子类DataSourceTransactionManager
注意:我们在mysql中开启事务,可以使用BEGIN、START TRANSACTION等,但是在源码中,我们并没有发现这样的sql语句。其实事务可以隐式开启,在上述doBegin方法的源码中,存在con.setAutoCommit(false)这样的方法,其实这就等价于执行sql语句set autocommit = OFF(隐式开启事务)
通过源码整理事务处理流程
TransactionAspectSupport#completeTransactionAfterThrowing
我们在上文中指出该TransactionAttribute类型为RuleBasedTransactionAttribut,然后如果我们指定@Transactional注解的rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性,后期会被解析成RollbackRuleAttribute对象,相关源码明细可以查看AbstractFallbackTransactionAttributeSource(AnnotationTransactionAttributeSource的父类)的getTransactionAttribute方法
当我们未指定上述四个属性,会调用super.rollbackOn的方法
当我们未指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性的时候,事务只有在遇到RuntimeException异常或者Error的时候才会回滚
当我们指定了四个属性中的一个或者多个,就会被解析成RollbackRuleAttribute(NoRollbackRuleAttribute),最后通过getDepth方法获取winner,如果winner是RollbackRuleAttribute旧回滚,否则就提交事务
getDepth方法主要判断抛出的异常与指定的异常之间的关系
depth值越小(大于0),优先级越高
不管最后事务是提交还是回滚,都会执行相应的触发器方法,我们可以利用这一特性,做一些扩展
mybatis相关接口会被JDK动态代理,代理对象的类型是MapperProxy。感兴趣小伙伴可以阅读我之前的博文 《@MapperScan源码解析》
MapperProxy#invoke
PlainMethodInvoker#invokeMapperMethod#executeSqlSessionTemplate#selectOne
SqlSessionInterceptor#invoke(sqlSessionProxy是一个代理对象,所以会进入相应拦截器方法)SqlSessionUtils#getSqlSession
我们看到了我们熟悉的TransactionSynchronizationManager,当mybatis执行sql的时候会从事务同步管理器里面获取resource,保证了同个事务里面的增删改查都是使用的同一个SqlSession