注解式事务
大家都知道想要 Spring 的事务功能,你就必须使用 @EnableAspectJAutoProxy 注解开启事务,并且需要再方法或者类或接口上标注 @Transactional 注解。
使用案例
@Configuration
@EnableTransactionManagement
@ComponentScan("com.cn.spring.service")
public class TxMainConfig {
@Bean
public DataSource dataSource() {
System.out.println("开始拦截数据源");
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/db?useSSL=false&allowPublicKeyRetrieval=true");
dataSource.setUsername("root");
dataSource.setPassword("8888");
System.out.println("数据源链接成功!");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// 事物管理器
@Bean
public JdbcTransactionManager jdbcTransactionManager() {
return new JdbcTransactionManager(dataSource());
}
@Bean
public TransactionTemplate transactionTemplate() {
TransactionTemplate template = new TransactionTemplate();
template.setTransactionManager(jdbcTransactionManager());
return template;
}
}
定义业务类如下:
@Service
public class CalculateServiceImpl implements CalculateService {
@Autowired
private OrderService orderService;
@Autowired
private StockService stockService;
@Override
@Transactional // 默认传播机制
public void add(int a, int b) {
try {
orderService.crateOrder();
stockService.addStock();
} catch (Exception e) {
e.printStackTrace();
}
}
}
那么思考下,为什么在 Spring 中标注了这两个注解(@EnableTransactionManagement、@Transactional)就能够
帮我们完成事务功能?
那么现在就看下这两个注解到底干了什么什么事情。先看 @EnableTransactionManagement 源码如下:
从源码可以清楚的看到通过 @Import 注解导入了两个核心类:InfrastructureAdvisorAutoProxyCreator、ProxyTransactionManagementConfiguration
其实猜都能猜到,第一个类肯定使用来生成代理类的、第二个肯定是导入一些事务相关切面逻辑。
进入到 InfrastructureAdvisorAutoProxyCreator 类可以发现此类是 AOP 切面功能 AbstractAutoProxyCreator 抽象类的子类,而且 AbstractAutoProxyCreator 是一个 BeanPostProcessor 后置处理器,在 Spring 启动过程中会被回调。
BeanPostProcessor 和 SmartInstantiationAwareBeanPostProcessor 接口
实现了上面接口的类,只需要关注接口中定义的规范 API 即可,在 Spring 启动过程中就会去回调这些接口的方法,源码如下:
然后去查看 InfrastructureAdvisorAutoProxyCreator 类看能否找到以上规范的 API 方法,发现找不到,但是它继承了 AbstractAdvisorAutoProxyCreator 类,所以就去它的父类中查找,发现也没有,那就在往上一个父类找,也就是 AbstractAutoProxyCreator (AOP 切面功能抽象类,AOP 中王者级别的类),发现终于找到了后置处理器中定义的规范 API 方法,源码如下:
首先看到 getEarlyBeanReference() 方法,源码如下:
发现第一行是把当前过来的原生包成品 bean 保存到了一个缓存中,为什么要保存呢?因为怕重复创建代理对象,第二行就是创建代理对象的逻辑,这个 wrapIfNecessary() 方法会在两个地方被调用,第一个就是这里的 getEarlyBeanReference() 方法,还有一个地方就是后置处理器的另一个 API 方法中——postProcessAfterInitialization() 方法中。
getEarlyBeanReference() 和 postProcessAfterInitialization() 都是后置处理器的规范 API,所以必然存在先后被回调到,所以这里不管你是哪个先被调用,你肯定需要一个缓存或者是标记说你干了什么事?这里就是使用一个 Map 类型 earlyProxyReferences 缓存表示在 getEarlyBeanReference() API 中创建过了代理对象,其他 API 中不需要再去创建了,第二个 API 调用创建代理对象的源码如下:
其他 API 不需要去关注,因为没有起到关键性作用,所以这里重点关注 getEarlyBeanReference() 和 postProcessAfterInitialization(),但是会发现这两个是做的同一件事情,所以只需要关注 getEarlyBeanReference() 这个 API 的逻辑即可。
进入到 wrapIfNecessary() 方法,源码如下:
这里有两个非常重要的步骤:
- 判断是否需要生成代理对象
- 创建代理对象
判断是否需要生成代理对象
进入到 getAdvicesAndAdvisorsForBean() 源码如下:
先看第一行代码,去收集实现了 Advisor 的接口子类,事务通过注解 @EnableTransactionManagement 导入了一个 Advisor,然后这里就会找到一个 Advisor 候选者,源码如下:
接着看第二行代码,就是找到的 Advisor 是否能够用于此类,能够能够用于此类,那就是表示这个类需要被增强,就需要被代理了,就需要创建这个类的代理类了。进入 findAdvisorsThatCanApply() 方法,会从方法和类进行匹配,源码如下:
获取都 ClassFilter 对象匹配类,答案是这里匹配不到,因为我们的 @Transactional 注解是标在了方法上的,所以又会往类的所有方法上去匹配,每个方法挨个去比较看是否能够找到 @Transactional 注解。
接下来我们进入事务注解的匹配过程,TransactionAttributeSourcePointcut 负责去匹配,看下到底是怎么匹配的,源码如下:
南海中一定要一个意识: 切面的匹配功过程一定会有 Pointcut 切入点,并且每个 Pointcut 一定会有两个元素:就是 ClassFilter 对类的匹配执行器,MethodMatcher 对方法的匹配执行器
进入到 getTransactionAttributeSource() 方法,源码如下:
为什么敢断定这个 transactionAttributeSource 变量不是 null ? 如下源码:在 TransactionAttributeSourceAdvisor 类的构造中赋值了这个 transactionAttributeSource 变量
这个 ProxyTransactionManagementConfiguration 是注解 @EnableTransactionManagement 导入进来的类。
然后 getTransactionAttributeSource() 就获取到了一个事务属性对象,transactionAttributeSource 这个对象主要用来封装 @Transactional 注解中解析出来的值(传播特性,隔离级别、只读等等)。
继续往下看,看是怎么匹配这个注解的
重点都是在 computeTransactionAttribute() 方法,这里面回去判断方法、类、接口是否被 @Transactional 修饰,进入源码:
进入 findTransactionAttribute() 方法,源码如下:
从上述源码中可以看出就是去查找这个类,方法、接口、接口方法中是否存在 @Transactional 注解,如果有就要开始往下代理对象了。
至此第一个步骤:匹配过程就算是结束了,接下来开始要去创建代理对象
创建代理对象
回到外层调用处,源码如下:
可以看到我们的目标对象被包装成了一个 SingletonTargetSource 对象,进入到 createProxy() 源码如下:
首先过来就创建了一个 ProxyFactory 的代理工厂,拷贝了一下当前对象的全部属性到工厂中,把目标对象放到了 ProxyFactory、Advisors 增强器放到了 ProxyFactory。这个对象包装了代理需要的所有东西,就是工厂,里面存放着代理相关的东西
还会推算你的目标 bean 是否有接口,如果没有就会使用 Cglib、有就使用 Proxy 代理,我们这里是有接口的,所以使用 Proxy 代理。
进入方法 getProxy() 内部,源码如下:
然后再进入 getProxy() 方法,源码如下:
这里就是 JDK 动态代理代码了,this 是上面创建的对象 JdkDynamicAopProxy,此类实现了 InvocationHandler 接口,源码如下:
至此创建代理对象也算完成了,代理对象的 InvocationHandler 是 JdkDynamicAopProxy,也就是说当你调用方法的时候,就会调用到 JdkDynamicAopProxy 类中的 invoke() 方法,这是动态代理最基本调用。
调用过程
测试代码如下:
因为这里的 CalculateService 类需要被代理,所以这里会生成代理对象,calculateService 对象是个$Proxy23@222 对象,当调用 add() 方法时,会调用到 JdkDynamicAopProxy 中的 invoke() 方法,因为 JdkDynamicAopProxy 实现了 InvocationHandler 接口,源码如下:
第一行其实去判断这个方法是否需要被增强,其实你走到这里,类的匹配根本就不需要做了,因为走到这里必然是代理类,现在只需要去匹配这个方法需不需被增强,进入 getInterceptorsAndDynamicInterceptionAdvice()方法,源码如下:
无非就是一个 matches() 匹配过程,如果匹配返回 true,表示这个方法需要被增强,就会进入 registry.getInterceptors(advisor) 方法,去获取对这个方法增强逻辑 Advice,源码如下:
发现是返回的一个 TransactionInterceptor transactionInterceptor 对象,其实就是一个 Advice,TransactionInterceptor 就是 Advice 的子类而已,而且这个 TransactionInterceptor 拦截器是通过 @EnableTransactionManagement 注解导入进来的,源码如下:
所以这个事物的所有增强逻辑都是在这里做的,这些增强逻辑其实就是帮我们自动开启事务,自动回滚,自动提交。所以这就是为什么我们直接添加两个注解能够帮我们完成事物控制。其实都是这块的 Advice 拦截器完成的。
至此 getInterceptorsAndDynamicInterceptionAdvice() 方法就算完成了,而且返回了一个 TransactionInterceptor Advice,再回到源码开始执行 proceed() 方法,源码如下:
此时 chain 不为空,就会执行 else 逻辑,进入到 ReflectiveMethodInvocation 类,源码如下:
执行 invoke() 方法,调用 TransactionInterceptor 事务提供的 Advice 增强功能,源码如下:
进入到 invokeWithinTransaction() 方法,源码如下:
首先过来就获取 @Transactional 解析封装好的事务对象 TransactionAttributeSource,然后获取事务管理器,继续往下看,源码如下:
看到这三个步骤是不是就恍然大悟了,原来事务相关的功能都是在这里帮我们做好了。
进入开启事务的方法 createTransactionIfNecessary() 方法,源码如下:
然后调用我们自己的业务方法,源码如下:
然后再看事务回滚的方法 completeTransactionAfterThrowing(),源码如下:
然后再看下事务提交的方法 commitTransactionAfterReturning() 源码如下:
至此声明式事务的执行流程分析已经算完成了,还有事物的传播特性没有分析,是因为这一块内容也挺多的,篇幅太大不太好,所以放在了另一篇文章中,下面看下编程式事务流程分析。
编程式事务
为什么还要这个编程式事务?因为很多时候注解不够灵活,如果自己来管控事务是不是比较好,而且一个注解开启的事物,如果业务逻辑中,有一个接口耗时非常的久,那么就会阻塞住 Connection 连接,连接没有释放,如果请求很多,那就完蛋了,资源可能就不够用了,但是编程式事务就比较好的可以自己控制。
代码如下:
// 编程式事务
@Override
public void reduce(int a, int b) {
System.out.println("start reduce......");
// 第一种方式: 什么都不用自己控制
template.execute(status -> {
// 这里面还可以继续嵌套事务
//orderService.crateOrder();
// ...
// 要是里面还有要执行的耗时逻辑又可以重新套一个execute 操作
// ...
return null;
});
template.execute(status -> {
// 这里面还可以继续嵌套事务
//stockService.addStock();
// ...
// 要是里面还有要执行的耗时逻辑又可以重新套一个execute 操作
// ...
return null;
});
// 第二种方式: 自己控制 commit 和 rollback
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
TransactionStatus transaction = jdbcTransactionManager.getTransaction(definition);
try {
System.out.println("一些耗时的业务逻辑代码.....");
stockService.addStock();
orderService.crateOrder();
} catch (TransactionException e) {
// 自己控制回滚操作
jdbcTransactionManager.rollback(transaction);
}
// 自己控制提交操作
jdbcTransactionManager.commit(transaction);
}
源码很简单,就对 execute() 这个分析,源码如下:
先去获取数据源对象,然后开启事务并设置手动提交,然后调用目标方法,执行完之后,调用 commit() 提交,如果出现异常或者 Error 就进行回滚。