seata的1.4.1版本已经发布,目前看到的最主要的变化如下:
1: 配置不需要使用registry.conf和file.conf了。而是可以通过Spring的配置参数配置。而且提供了默认值,只需要按照业务修改自己的配置即可。
2:DataSourceProxy 实例也不需要创建,seata会自动创建。
1:使用:
在Spring环境下,使用seata只需要引入 seata-spring-boot-starter 的JAR包即可。默认的自动配置加载类是:SeataAutoConfiguration。
2:在 SeataAutoConfiguration 里,会加载一堆的配置文件,文件比较多,截图如下:
这一堆的配置信息,会存放在 StarterConstants#PROPERTY_BEAN_MAP 这个map对象里。
3:数据源后置处理器:SeataDataSourceBeanPostProcessor。在SeataAutoConfiguration会创建 SeataDataSourceBeanPostProcessor 的实例,如下:
@Bean(BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR)
@ConditionalOnMissingBean(SeataDataSourceBeanPostProcessor.class)
public SeataDataSourceBeanPostProcessor seataDataSourceBeanPostProcessor(SeataProperties seataProperties) {
return new SeataDataSourceBeanPostProcessor(seataProperties.getExcludesForAutoProxying(), seataProperties.getDataSourceProxyMode());
}
该后置处理器主要实现了postProcessAfterInitialization接口,其目的是在bean初始化后,根据seata的模式创建不同的数据库代理对象,比如:非XA模式创建的是DataSourceProxy,XA模式创建的是DataSourceProxyXA。并将其保存到全局对象中。
这里有意思的是,postProcessAfterInitialization 接口返回的时候原始 DataSource 对象。所以其他服务引用的话,不会得到代理数据库代理对象。
通过这里代码,所以我们没有必要自己创建类DataSourceProxy的bean对象。
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSource) {
//When not in the excludes, put and init proxy.
if (!excludes.contains(bean.getClass().getName())) {
//Only put and init proxy, not return proxy.
DataSourceProxyHolder.get().putDataSource((DataSource) bean, dataSourceProxyMode);
}
//If is SeataDataSourceProxy, return the original data source.
if (bean instanceof SeataDataSourceProxy) {
return ((SeataDataSourceProxy) bean).getTargetDataSource();
}
}
return bean;
}
4:代理创建器 SeataAutoDataSourceProxyCreator。在SeataAutoConfiguration会创建该类的实例,代码如下:
@Bean(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR)
@ConditionalOnMissingBean(SeataAutoDataSourceProxyCreator.class)
public SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) {
return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(),
seataProperties.getExcludesForAutoProxying(), seataProperties.getDataSourceProxyMode());
}
该类继承了AbstractAutoProxyCreator,其目的是创建对DataSource对象创建拦截器(SeataAutoDataSourceProxyAdvice)。通过下面代码可知,只有DataSrouce对象,而且是非SeataProxy对象才创建。这里在后面的代码里会用到。
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
return !DataSource.class.isAssignableFrom(beanClass) ||
SeataProxy.class.isAssignableFrom(beanClass) ||
excludes.contains(beanClass.getName());
}
5:事务扫描器GlobalTransactionScanner。在SeataAutoConfiguration会创建该类的实例,代码如下:
其构造参数如下:
applicationId: 取配置seata.application-id的值,如果没有,则取spring.application.name配置
txServiceGroup:取配置seata.tx-service-group的值,如果没有,则值为 applicationId的值加字符串“-seata-service-group”
@Bean
@DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})
@ConditionalOnMissingBean(GlobalTransactionScanner.class)
public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Automatically configure Seata");
}
return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup(), failureHandler);
}
6: GlobalTransactionScanner继承了接口InitializingBean接口,所以会调用接口 afterPropertiesSet 进行初始化操作。在这里主要是会初始化netty客户端。后面会有问题专门介绍seata里的netty使用。代码如下:
TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);
RMClient.init(applicationId, txServiceGroup);
7:GlobalTransactionScanner 继承了AbstractAutoProxyCreator接口,其主要是实现代理。在方法 wrapIfNecessary 中。对注解@GlobalTransactional 和 @GlobalLock ,创建了拦截器 GlobalTransactionalInterceptor。如果方法有这两个两个钟的其中一个注解,那么实际得到的是一个拦截器。该拦截器的方法 initDefaultGlobalTransactionTimeout 中会初始化事务的超时时间(读取的是配置 client.tm.defaultGlobalTransactionTimeout,默认值 60秒,这里没有改造得彻底,按理是可以根据Spring配置,)代码如下:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
try {
synchronized (PROXYED_SET) { //忽略了部分代码
interceptor = null;
Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
if (!existsAnnotation(new Class[]{serviceInterface})
&& !existsAnnotation(interfacesIfJdk)) {
return bean;
}
if (interceptor == null) {
if (globalTransactionalInterceptor == null) {
globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
ConfigurationCache.addConfigListener( ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
(ConfigurationChangeListener)globalTransactionalInterceptor);
}
interceptor = globalTransactionalInterceptor;
}
if (!AopUtils.isAopProxy(bean)) {
bean = super.wrapIfNecessary(bean, beanName, cacheKey);
} else {
AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
for (Advisor avr : advisor) {
advised.addAdvisor(0, avr);
}
}
PROXYED_SET.add(beanName);
return bean;
}
} catch (Exception exx) {
throw new RuntimeException(exx);
}
}
8:如果方法有注解 @GlobalTransactional,则调用的时候,会执行拦截器方法:GlobalTransactionalInterceptor#invoke。在这个方法里,会判断调用方法是有有注解 @GlobalTransactional 或者 @GlobalLock,对@GlobalTransactional注解,调用 handleGlobalTransaction 方法进行出来,代码如下:
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
Class<?> targetClass =
methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;
Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
final GlobalTransactional globalTransactionalAnnotation =
getAnnotation(method, targetClass, GlobalTransactional.class);
final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);
boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);
if (!localDisable) {
if (globalTransactionalAnnotation != null) {
return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);
} else if (globalLockAnnotation != null) {
return handleGlobalLock(methodInvocation, globalLockAnnotation);
}
}
}
return methodInvocation.proceed();
}
9:在handleGlobalTransaction方法,有个重要的接口 TransactionalExecutor。他的默认实现如下:
name()方法:获取事务的名词,如果事务的 name 属性有值,则用该值。如果没有,则根据方法名加上参数进行拼接。
getTransactionInfo()方法就是解析注解 @GlobalTransactional。比如:事务的超时时间,事务名词,事务的传播性(默认是是:REQUIRED),事务的重试配置,
事务的回滚异常和不回滚异常等。代码如下:
return transactionalTemplate.execute(new TransactionalExecutor() {
@Override
public Object execute() throws Throwable {
return methodInvocation.proceed();
}
public String name() {
String name = globalTrxAnno.name();
if (!StringUtils.isNullOrEmpty(name)) {
return name;
}
return formatMethod(methodInvocation.getMethod());
}
@Override
public TransactionInfo getTransactionInfo() {
int timeout = globalTrxAnno.timeoutMills();
if (timeout <= 0 || timeout == DEFAULT_GLOBAL_TRANSACTION_TIMEOUT) {
timeout = defaultGlobalTransactionTimeout;
}
TransactionInfo transactionInfo = new TransactionInfo();
transactionInfo.setTimeOut(timeout);
transactionInfo.setName(name());
transactionInfo.setPropagation(globalTrxAnno.propagation());
transactionInfo.setLockRetryInternal(globalTrxAnno.lockRetryInternal());
transactionInfo.setLockRetryTimes(globalTrxAnno.lockRetryTimes());
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
for (Class<?> rbRule : globalTrxAnno.rollbackFor()) {
rollbackRules.add(new RollbackRule(rbRule));
}
for (String rbRule : globalTrxAnno.rollbackForClassName()) {
rollbackRules.add(new RollbackRule(rbRule));
}
for (Class<?> rbRule : globalTrxAnno.noRollbackFor()) {
rollbackRules.add(new NoRollbackRule(rbRule));
}
for (String rbRule : globalTrxAnno.noRollbackForClassName()) {
rollbackRules.add(new NoRollbackRule(rbRule));
}
transactionInfo.setRollbackRules(rollbackRules);
return transactionInfo;
}
});
10:在 handleGlobalTransaction 方法里,会调用 TransactionalTemplate 对象的excute 方法。该方法就是事务的核心实现。代码如下
public Object execute(TransactionalExecutor business) throws Throwable {
// 1. 获取事务信息,就是上面介绍的方法
TransactionInfo txInfo = business.getTransactionInfo();
if (txInfo == null) {
throw new ShouldNeverHappenException("transactionInfo does not exist");
}
// 1.1 获取当前的事务,如果是TM,则得到的值是null。 如果是RM,则得到的是 DefaultGlobalTransaction 对象(有xID)。
GlobalTransaction tx = GlobalTransactionContext.getCurrent();
// 1.2 获取事务的传播性,默认是REQUIRED
Propagation propagation = txInfo.getPropagation();
SuspendedResourcesHolder suspendedResourcesHolder = null;
try { // 这里省略了事务传播性的处理
// 1.3 If null, create new transaction with role 'GlobalTransactionRole.Launcher'.
// 对于TM,创建DefaultGlobalTransaction,但是该对象没xID,角色是:Launcher
if (tx == null) {
tx = GlobalTransactionContext.createNew();
}
// 将事务信息保存到当前线程
GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);
try {
// 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,
//else do nothing. Of course, the hooks will still be triggered.
beginTransaction(txInfo, tx); // 提交事务
Object rs;
try {
// Do Your Business
rs = business.execute(); // 执行业务方法
} catch (Throwable ex) {
// 3. The needed business exception to rollback.
completeTransactionAfterThrowing(txInfo, tx, ex); //异常后的事务处理
throw ex;
}
// 4. everything is fine, commit.
commitTransaction(tx); //提交事务
return rs;
} finally {
//5. clear
resumeGlobalLockConfig(previousConfig);
triggerAfterCompletion();
cleanUp();
}
} finally {
// If the transaction is suspended, resume it.
if (suspendedResourcesHolder != null) {
tx.resume(suspendedResourcesHolder);
}
}
}
11:开始事务就是调用 DefaultGlobalTransaction#begin方法。该方法主要的功能就是使用netty调用 seata-server,生成事务ID,及XID,并把这个保存在线程上下文中。代码如下:
private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
try {
triggerBeforeBegin();
tx.begin(txInfo.getTimeOut(), txInfo.getName());
triggerAfterBegin();
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,TransactionalExecutor.Code.BeginFailure);
}
}
public void begin(int timeout, String name) throws TransactionException {
//忽略不重要的代码
xid = transactionManager.begin(null, null, name, timeout);
status = GlobalStatus.Begin;
RootContext.bind(xid);
}
12:在方法 commitTransaction 里就是提交事务。该方法的主要作用就是使用 netty请求 seata-server 端,告诉服务端,事务结束。并清除线程上下文里面的XID,代码如下:
private void commitTransaction(GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
try {
triggerBeforeCommit();
tx.commit();
triggerAfterCommit();
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,TransactionalExecutor.Code.CommitFailure);
}
}
public void commit() throws TransactionException {
int retry = COMMIT_RETRY_COUNT <= 0 ? DEFAULT_TM_COMMIT_RETRY_COUNT : COMMIT_RETRY_COUNT;
try {
while (retry > 0) {
try {
status = transactionManager.commit(xid);
break;
} catch (Throwable ex) {
retry--;
if (retry == 0) {
throw new TransactionException("Failed to report global commit", ex);
}
}
}
} finally {
if (xid.equals(RootContext.getXID())) {
suspend();
}
}
}
public GlobalStatus commit(String xid) throws TransactionException {
GlobalCommitRequest globalCommit = new GlobalCommitRequest();
globalCommit.setXid(xid);
GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit);
return response.getGlobalStatus();
}
13:completeTransactionAfterThrowing 方法里,就是判断当前抛出的异常是否需要回滚。如果需要回滚,通过netty调用 seata-server,通知事务回滚。如果不是需要回滚的异常,则处理和上面的事务提交一致。
private void completeTransactionAfterThrowing(TransactionInfo txInfo, GlobalTransaction tx, Throwable originalException) throws TransactionalExecutor.ExecutionException {
if (txInfo != null && txInfo.rollbackOn(originalException)) {
try {
rollbackTransaction(tx, originalException);
} catch (TransactionException txe) {
// Failed to rollback
throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.RollbackFailure, originalException);
}
} else {
// not roll back on this exception, so commit
commitTransaction(tx);
}
}
14:seata的重要功能就是事务ID的传播。在seata-spring-boot-starter的1.4.1版本,调用的时候,使用RestTemplate调用远程服务时,传递XID的拦截器默认没有。在spring-cloud-starter-alibaba-seata的2.2.5.RELEASE版本有该拦截器,只是对应的seata是1.3.0版本。所以在使用的1.4.1版本的时候,我们自己定义拦截器,代码如下:
public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest);
String xid = RootContext.getXID();
if (!StringUtils.isEmpty(xid)) {
requestWrapper.getHeaders().add(RootContext.KEY_XID, xid);
}
return clientHttpRequestExecution.execute(requestWrapper, bytes);
}
}
@Configuration
public class SeataConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Bean
public SeataRestTemplateInterceptor seataRestTemplateInterceptor() {
return new SeataRestTemplateInterceptor();
}
@Autowired(required = false)
private Collection<RestTemplate> restTemplates;
@Autowired
private SeataRestTemplateInterceptor seataRestTemplateInterceptor;
@PostConstruct
public void init() {
if (this.restTemplates != null) {
for (RestTemplate restTemplate : restTemplates) {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(
restTemplate.getInterceptors());
interceptors.add(this.seataRestTemplateInterceptor);
restTemplate.setInterceptors(interceptors);
}
}
}
}
15:在seata的事务参与方,就是RM,那么需要知道调用本服务的时候,请求消息头里面有没有XID,所以有拦截器专门进行了这样的操作,代码如下:
@Configuration
@ConditionalOnWebApplication
public class HttpAutoConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TransactionPropagationInterceptor());
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add(new HttpHandlerExceptionResolver());
}
}
public class TransactionPropagationInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String xid = RootContext.getXID();
String rpcXid = request.getHeader(RootContext.KEY_XID);
if (xid == null && rpcXid != null) {
RootContext.bind(rpcXid);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) {
if (RootContext.inGlobalTransaction()) {
XidResource.cleanXid(request.getHeader(RootContext.KEY_XID));
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。