• Spring注解驱动之声明式事务源码分析


    概述

    环境搭建

    导入相关依赖

    <dependency>
        <groupId>c3p0groupId>
        <artifactId>c3p0artifactId>
        <version>0.9.1.2version>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>5.1.44version>
    dependency>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-jdbcartifactId>
        <version>4.3.12.RELEASEversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    IOC容器中注册一个c3p0数据源。

    package com.meimeixia.tx;
    
    import javax.sql.DataSource;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    @EnableTransactionManagement // 它是来开启基于注解的事务管理功能的 
    @ComponentScan("com.meimeixia.tx")
    @Configuration
    public class TxConfig {
    
    	// 注册c3p0数据源
    	@Bean
    	public DataSource dataSource() throws Exception {
    		ComboPooledDataSource dataSource = new ComboPooledDataSource();
    		dataSource.setUser("root");
    		dataSource.setPassword("liayun");
    		dataSource.setDriverClass("com.mysql.jdbc.Driver");
    		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
    		return dataSource;
    	}
    	
    // 向IOC容器中注册一个JdbcTemplate组件,它是spring提供的一个简化数据库操作的工具。
    ```java
    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }
    
    // 注册事务管理器在容器中
    @Bean
    public PlatformTransactionManager platformTransactionManager() throws Exception {
    	return new DataSourceTransactionManager(dataSource());
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    注意,这个事务管理器有一个特别重要的地方,就是它要管理数据源,也就是说事务管理器一定要把数据源控制住。这样的话,它才会控制住数据源里面的每个连接,这时该连接上的回滚以及事务的开启等操作,都会有这个事务管理器来做。

    写sql相关操作类

    package com.meimeixia.tx;
    
    import java.util.UUID;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class UserDao {
    	@Autowired
    	private JdbcTemplate jdbcTemplate;
    	
    	public void insert() {
    		String sql = "insert into `tbl_user`(username, age) values(?, ?)";
    		String username = UUID.randomUUID().toString().substring(0, 5);
    		jdbcTemplate.update(sql, username, 19); // 增删改都来调用这个方法
    	}
    }
    
    package com.meimeixia.tx;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService {
    
    	@Autowired
    	private UserDao userDao;
    	
    	@Transactional
    	public void insertUser() {
    	userDao.insert();
    	// otherDao.other(); // 该方法中的业务逻辑势必不会像现在这么简单,肯定还会调用其他dao的方法
    	System.out.println("插入完成...");
    	
    	int i = 10 / 0;
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    声明式事务原理

    要想指定声明式事务的原理,只需要搞清楚@EnableTransactionManagement注解给容器中注册了什么组件,以及这些组织工作时候的功能是什么就行了。一旦把这个研究透了,声明式事务的原理就清楚了。
    之前研究AOP的原理的时候,是从@EnableAspectJAutoProxy注解开始入手研究的,因此研究声明式事务的原理,我们也应该从@EnableTransactionManagement注解开始入手研究。
    其实,当研究完声明式事务的原理,就会发现这一过程与研究AOP原理的过程是非常相似的,也可以说着俩原理几乎一摸一样。

    @EnableTransactionManagement注解利用TransactionManagementConfigurationSelector给容器中导入组件

    在配置类上添加@EnableTransactionManagement注解,便能够开启基于注解的事务管理功能。看看下面的源码。
    在这里插入图片描述
    从源码中可以看出,@EnableTransactionManagement注解使用@Import注解给容器中引入了TranactionManagementConfigurationSelector组件。其实TranactionManagementConfigurationSelector就是一个ImportSelector。
    我们可以点到TransactionManagementConfigurationSelector类中一看究竟,如下图所示,发现它继承了一个类,叫AdviceModeImportSelector。
    在这里插入图片描述
    然后再次点到AdviceModeImportSelector类中,如下图所示,发现它实现了一个接口,叫ImportSelector。
    在这里插入图片描述
    说到底,其实它就是用于给容器中快速导入一些组件的,到底要导入哪些组件,就看它会返回哪些要导入到容器中的组件的全类名。
    看源码就会发现里面会做一个switch判断,如果adviceMode是PROXY,就会返回一个String[],该String数组如下所示:

    new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
    
    • 1

    这说明会向容器中导入AutoProxyRegistrar和ProxyTransactionManagementConfiguration这两个组件。

    导入第一个组件(AutoProxyRegistrar)做了什么

    在这里插入图片描述AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,说明AutoProxyRegistrar组件是来向容器中注册bean的,最终会调用该组件的registerBeanDefinitions()方法来向容器中注册bean。

    AutoProxyRegistrar向容器中注入一个自动代理创建器InfrastructureAdvisorAutoProxyCreator

    我们来仔细的看下AutoProxyRegistrar类中的registerBeanDefinitions()方法。
    在这里插入图片描述
    在该方法中先是通过如下一行代码来获取各种注解类型,这儿需要特别注意的是,这里是拿到所有的注解类型,而不是只拿@EnableAspectJAutoProxy这个类型的。因为mode、proxyTargetClass等属性会直接影响到代理的方式,而拥有这些属性的注解至少有@EnableTransactionManagement、@EnableAsync以及@EnableCaching等等,甚至还有启用AOP的注解,即@EnableAspectJAutoProxy,它也能设置proxyTargetClass这个属性的值,因此也会产生关联影响。

    Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
    
    • 1

    然后是拿到注解里的mode、proxyTargetClass这两个属性的值。
    在这里插入图片描述
    注意,如果这儿的注解是@Configuration或者别的其他注解的话,那么获取到的这俩属性的值就是null了。
    接着做一个判断,如果存在mode、proxyTargetClass这两个属性,并且这两个属性的class类型也都是对的,那么便会进入到if判断语句中,这样,其余注解就相当于都被挡在外面了。
    在这里插入图片描述
    要是真进入到了if判断语句中,是不是意味着找到了候选的注解(例如@EnableTransactionManagement)呢?你仔细想一下,是不是这回事。找到了候选的注解之后,就将candidateFound标识置为true。

    紧接着会再做一个判断,即判断找到的候选注解中的mode属性的值是否为AdviceMode.PROXY,若是则会调用我们熟悉的AopConfigUtils工具类的registerAutoProxyCreatorIfNecessary方法。相信大家也很熟悉这个方法了,它主要是来向容器中注册一个InfrastructureAdvisorAutoProxyCreator组件的。
    在这里插入图片描述
    我们继续往下看AutoProxyRegistrar类的registerBeanDefinitions()方法。这时,又会做一个判断,要是找到的候选注解设置了proxyTargetClass这个属性的值,并且值为true,那么便会进入到下面的if判断语句中,看要不要强制使用CGLIB的方式。

    如果此时找到的候选注解是@EnableTransactionManagement,想一想会发生什么事情?查看该注解的源码,你会发现它里面就拥有一个proxyTargetClass属性,并且其默认值是false。所以此时压根就不会进入到if判断语句中,而只会调用我们熟悉的AopConfigUtils工具类的registerAutoProxyCreatorIfNecessary方法。
    继续追踪源码我们就发现注册的是一个InfrastructureAdvisorAutoProxyCreator.class了。
    在这里插入图片描述
    现在我们得出一个结论:导入的第一个组件(AutoProxyRegistrar)向容器中注入了一个自动代理创建器,即InfrastructureAdvisorAutoProxyCreator。
    我们继续追踪源码发现InfrastructureAdvisorAutoProxyCreator它其实是一个后置处理器。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    InfrastructureAdvisorAutoProxyCreator组件的功能

    其实InfrastructureAdvisorAutoProxyCreator做的事情和之前研究的AOP的AnnotationAwareAspectJAutoProxyCreator组件所有的事情基本一样。就是利用后置处理器机制在对象创建以后进行包装,然后返回一个代理对象,并且该代理对象里面会有所有的事务增强器。最后代理对象执行目标方法,在执行的目标方法的过程过程中会利用拦截器的链式机制,依次进入每个拦截器中进行执行。

    导入第二个组件(ProxyTransactionManagementConfiguration)的作用

    向容器中注册事务增强器

    源码中我们发现它是一个配置类,它会利用@Bean注解向容器中注册各种组件,而且注册的第一个组件就是BeanFactoryTransactionAttributeSourceAdvisor,这个Advisor是事务的核心内容,称之为事务增强器。
    在这里插入图片描述

    向容器中注册事务增强器时,要用到事务属性源

    事务增强器是什么呢?从上面的配置类可以看出,在向容器中注册事务增强器时,它会需要一个TransactionAttributeSource,翻译过来应该是事务属性源。
    你会发现所需的TransactionAttributeSource又是容器中的一个bean,它是一个AnnotationTransactionAttributeSource对象。这个是重点,它是基于注解驱动的事务管理的事务属性源,和@Transaction注解相关,也是现在使用最多的方式,其基本作用遇上比如@Transaction注解标准的方法是,此类会分析此事务注解。
    继续追踪源码会发现有一个事务解析器接口。
    在这里插入图片描述
    相关属性就是我们在@Transaction注解里面的写的。
    在这里插入图片描述

    小结

    事务增强器要用到事务注解的信息,它会使用AnnotationTransactionAttributeSource类来解析方法上标注的@Transtaction注解。

    在向容器中注册事务增强器时,还需要用到事务拦截器

    接下来,我们再来看看向容器中注册事务增强器时,还得做些什么。回到ProxyTransactionManagementConfiguration类中,发现在向容器中注册事务增强器时,除了需要事务注解信息,还需要一个事务的拦截器,看到那个transactionInterceptor方法没,它就是表示事务增强器还要用到一个事务的拦截器。
    在这里插入图片描述
    仔细查看上面的transactionInterceptor方法,你会看到在里面创建了一个TransactionInterceptor对象,创建完毕之后,不但会将事务属性源设置进去,而且还会将事务管理器(txManager)设置进去。也就是说,事务拦截器里面不仅保存了事务属性信息,还保存了事务管理器。

    我们点进去TransactionInterceptor类里面去看一下,发现该类实现了一个MethodInterceptor接口,如下图所示。
    在这里插入图片描述
    看到它,会不会倍感亲切,因为在研究AOP原理的时候,就认识它了。AOP里面的一个知识点:切面类里面的通知方法最终都会被整成增强器,而增强器又会被转成MethodInterceptor。所以,这个事务拦截器的本质上还是一个MethodInterceptor(方法拦截器)。
    所谓方法拦截器就是会在容器中放一个代理对象,代理对象要执行目标方法,那么方法拦截器就会进行工作。
    其实,跟我妈之前研究AOP的原理一摸一样,在代理对象执行目标方法的时候,它便会来执行拦截器链,而现在这个拦截器链,只有一个TransactionInterceptor,它正是这个拦截器。
    仔细翻阅TransactionInterceptor类的源码,你会发现它里面有一个invoke方法,而且还会看到在该方法里面又调用了一个invokeWithinTransaction方法,如下图所示。
    在这里插入图片描述
    在这里插入图片描述

    先来获取事务相关的属性信息

    从invokeWithinTransaction方法的第一行代码,即:

    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    
    • 1
    再来获取PlatformTransactionManager

    接着往下看invokeWithinTransaction方法,可以看到它的第二行代码是这样写的:

    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    
    • 1

    这就是来获取PlatformTransactionManager的,还记得我们之前就已经向容器中注册了一个吗,现在就是来获取它的。那到底又是怎么来获取的呢?我们不妨点进去determineTransactionManager方法里面去看一下。
    在这里插入图片描述
    首次获取肯定就为null,但没关系,因为最终会从容器中按照类型来获取,这可以从下面这行代码中看出来。

    defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
    
    • 1

    所以,我们只需要给容器中注入一个PlatformTransactionManager,正如我们前面写的这样:

    // 注册事务管理器在容器中
    @Bean
    public PlatformTransactionManager platformTransactionManager() throws Exception {
        return new DataSourceTransactionManager(dataSource());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    总结

    如果事先没有添加任何TransactionManager,那么最终会从容器中按照类型来获取一个PlatformTransactionManager。

    执行目标方法

    接下来,继续往下看invokeWithinTransaction方法,来看它接下去又做了些什么。其实,很容易就能看出来,获取到事务管理器之后,然后便要来执行目标方法了,而且如果目标方法执行时一切正常,那么还能拿到一个返回值,如下图所示。
    在这里插入图片描述

    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    
    • 1

    面这个方法翻译成中文,就是如果是必须的话,那么得先创建一个Transaction。说人话,就是如果目标方法是一个事务,那么便开启事务。

    如果目标方法执行时一切正常,那么接下来该怎么办呢?这时,会调用一个叫commitTransactionAfterReturning的方法,如下图所示。
    在这里插入图片描述
    我们可以点进去commitTransactionAfterReturning方法里面去看一看,发现它是先获取到事务管理器,然后再利用事务管理器提交事务,如下图所示。
    在这里插入图片描述
    如果执行目标方法时出现异常,那么又该怎么办呢?这时,会调用一个叫completeTransactionAfterThrowing的方法,如下图所示。
    在这里插入图片描述
    我们可以点进去completeTransactionAfterThrowing方法里面去看一看,发现它是先获取到事务管理器,然后再利用事务管理器回滚这次操作,如下图所示。
    在这里插入图片描述
    也就是说,真正的回滚与提交事务的操作都是由事务管理器来做的,而TransactionInterceptor只是用来拦截目标方法的。

    以上就是我们通过简单地来分析源码,粗略地了解了一下整个事务控制的原理。

    总结

    首先,使用AutoProxyRegistrar向Spring容器里面注册了一个后置处理器,这个后置处理器会负责给我们包装代理对象。然后使用ProxyTransactionManagementConfiguration再向容器中注册一个事务增强器,此时需要用的事务拦截器。最后代理对象执行目标方法,在执行目标方法的过程中便会执行代理类里面的拦截器链,而且每次在执行目标方法异常时,便会利用事务管理器进行事务回滚,如果执行一切正常,那么就好使用事务管理器提交事务。

    参考

    Spring注解驱动开发第34讲——你了解基于注解版的声明式事务吗?

    Spring注解驱动开发第35讲——声明式事务原理的源码分析

  • 相关阅读:
    网络安全(黑客)自学
    自动化测试之路 —— Appium使用教程
    MATLAB中zticks函数用法
    程序的编译汇编和链接
    Jvm上如何运行其他语言?JSR223规范最详细讲解
    业务开发时,接口不能对外暴露的解决方案
    【机器学习周志华】读书笔记 P2 假设空间与归纳偏好
    盲注原理基础
    【Aurora 8B/10B IP(1)--初步了解】
    JVM学习-Class文件结构
  • 原文地址:https://blog.csdn.net/tianzhonghaoqing/article/details/126911822