seata客户端 1.4.1版本源码(一)

seata的1.4.1版本已经发布,目前看到的最主要的变化如下:
1: 配置不需要使用registry.conf和file.conf了。而是可以通过Spring的配置参数配置。而且提供了默认值,只需要按照业务修改自己的配置即可。
2:DataSourceProxy 实例也不需要创建,seata会自动创建。

1:使用:
在Spring环境下,使用seata只需要引入 seata-spring-boot-starter 的JAR包即可。默认的自动配置加载类是:SeataAutoConfiguration。

2:在 SeataAutoConfiguration 里,会加载一堆的配置文件,文件比较多,截图如下:
seata客户端 1.4.1版本源码(一)_第1张图片
seata客户端 1.4.1版本源码(一)_第2张图片
这一堆的配置信息,会存放在 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 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 restTemplates;
 @Autowired
 private SeataRestTemplateInterceptor seataRestTemplateInterceptor;
 @PostConstruct
 public void init() {
        if (this.restTemplates != null) {
            for (RestTemplate restTemplate : restTemplates) {
                List interceptors = new ArrayList(
                        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 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));
        }
    }
}

你可能感兴趣的:(javaseata)