前言
作者使用的开发套件是Spring Cloud Alibaba,RPC框架采用Dubbo Spring Cloud,使用Feign的同学也可以看看,原理相通,大同小异。
准备阶段
- 配置数据库,创建Seata回滚需要的表UNDO_LOG和业务表
- 启动Seata Server
这部分建议阅读 官方文档-README
核心原理
Seata 全局事务的传播机制就是指事务上下文的传播,根本上,就是 XID 的应用运行时的传播方式。默认的,RootContext 的实现是基于 ThreadLocal 的,即 XID 绑定在当前线程上下文中。
而我们在分布式系统中,各系统不在一个线程中,为了使Seata全局事物能够在分布式系统中传播,需要对此进行扩展,Seata内置了Dubbo的支持,引用官方文档中的描述:
对 Dubbo 的支持,我们利用了 Dubbo 框架的 org.apache.dubbo.rpc.Filter 机制。
这部分建议阅读 官网文档-微服务框架支持
开始配置
通过阅读Feign + Seata的配置教程,可以得知我们需要配置的就是将GlobalTransactionScanner
和DataSourceProxy
加入Spring容器
GlobalTransactionScanner
的配置较为简单,而配置DataSourceProxy
网上主流的方式是手动配置,如:
DruidDataSource dataSource = initDataSource(dataSourceProps.get("url").toString(), dataSourceProps.get("username").toString(), dataSourceProps.get("password").toString());
DataSourceProxy proxy = new DataSourceProxy(dataSource);
这种方式的确简单,却使很多配置项丢失,不是完美的解决方式。完美的解决方式应为在Spring容器初始化后用new DataSourceProxy(dataSource)替换原有dataSource对象。
那么应如何替换呢,我们可以通过实现Spring提供的ApplicationContextAware接口来获取ApplicationContext。而ApplicationContext值有getBean(),而不能移除之前的dataSource。我们先看一张继承关系图:
虽然ApplicationContext不能直接向下转型为DefaultListableBeanFactory,但我们可以通过ApplicationContext中持有的AutowireCapableBeanFactory向下转型为DefaultListableBeanFactory,从而通过它来获取、删除和注册Bean。
说了这么多,最后贴我的配置类
@ConditionalOnClass(GlobalTransactionScanner.class)
@AutoConfigureBefore(name = "mybatisPlusAutoConfiguration")
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
@Configuration
public class SeataAutoConfiguration implements ApplicationContextAware {
@Value("${spring.application.name}")
private String applicationName;
private String groupName = "my_test_tx_group";
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
if (applicationName == null) {
return new GlobalTransactionScanner(groupName);
} else {
return new GlobalTransactionScanner(applicationName, groupName);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 获取SpringBoot自动配置的DataSource
DataSource originDataSource = applicationContext.getBean(DataSource.class);
log.debug("originDataSource = {}", applicationContext.getBean("dataSource"));
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
// 从Spring容器中删除原dataSource
defaultListableBeanFactory.removeBeanDefinition("dataSource");
// 将原数据源包装成DataSourceProxy
DataSourceProxy dataSourceProxy = new DataSourceProxy(originDataSource);
// 向Spring容器中注册DataSourceProxy
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DataSource.class, () -> dataSourceProxy);
defaultListableBeanFactory.registerBeanDefinition("dataSource", beanDefinitionBuilder.getBeanDefinition());
log.debug("proxyDataSource = {}", applicationContext.getBean("dataSource"));
}
}
参考链接:
Seata Wiki
Spring Cloud Alibaba Seata示例项目
Feign + Seata配置教程
Spring IOC 容器源码分析