我们知道seata对跨服务之间对多个分支事务进行连接的原理是通过传播xid实现的,但是有一个问题就是如果我们对某一个分支事务的接口单独进行调用,也就是此时是不需要使用到seata的功能,比如说seata的AT模式,因为AT模式的实现原理是通过对数据源进行的代理,所以按照常理来说如果在单独调用分支事务接口的情况下必然仍旧会走AT模式的流程(前置镜像,后置镜像,往TC注册分支事务),但是此时我们是不希望走AT模式的流程的,那么seata是如何解决这个问题的呢?下面我们具体来探讨一下
数据源自动配置-SeataDataSourceAutoConfiguration
@ConditionalOnBean(DataSource.class)
@ConditionalOnExpression("${seata.enabled:true} && ${seata.enableAutoDataSourceProxy:true} && ${seata.enable-auto-data-source-proxy:true}")
@AutoConfigureAfter({SeataCoreAutoConfiguration.class})
public class SeataDataSourceAutoConfiguration {
/**
* The bean seataAutoDataSourceProxyCreator.
*/
@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());
}
}
在seata-spring-boot-starter模块中,SeataDataSourceAutoConfiguration这个自动配置类往spring容器中加入了SeataAutoDataSourceProxyCreator这个组件
public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator {
// ......
}
点进去一看可以看到SeataAutoDataSourceProxyCreator继承了spring的AbstractAutoProxyCreator,熟悉spring的同学可能知道AbstractAutoProxyCreator这个组件,该组件实际上是一个BeanPostProcessor,spring在需要生成一些bean的代理对象通常都会使用该组件进行支持实现,比如spring事务,spring cache,spring异步等这些功能的底层实现都是基于该组件进行支持的,其核心的wrapIfNecessary方法就是在提供给用户的一个扩展方法,可以根据自己的需要重写该方法来返回一个自己定制的bean,所以seata也不例外,下面是SeataAutoDataSourceProxyCreator重写的wrapIfNecessary方法
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 如果不是数据源bean则直接返回,这里不关注
if (!(bean instanceof DataSource)) {
return bean;
}
// 条件成立:说明这个bean是一个普通的数据源bean
if (!(bean instanceof SeataDataSourceProxy)) {
Object enhancer = super.wrapIfNecessary(bean, beanName, cacheKey);
// 这意味着这个bean要么被用户排除在外,要么以前是代理
if (bean == enhancer) {
return bean;
}
// 构建这个数据源bean的代理对象
DataSource origin = (DataSource) bean;
SeataDataSourceProxy proxy = buildProxy(origin, dataSourceProxyMode);
// key=>原始的数据源,value=>代理后的数据源
DataSourceProxyHolder.put(origin, proxy);
// 返回代理后的数据源bean
return enhancer;
}
/*
* 手动注册数据源代理
* 当尝试自己注册SeataDataSourceProxy bean时,事情会变得危险!
* 如果你坚持这样做,你必须确保你的方法返回类型是DataSource,因为这个处理器永远不会返回SeataDataSourceProxy的任何子类
*/
LOGGER.warn("Manually register SeataDataSourceProxy(or its subclass) bean is discouraged! bean name: {}", beanName);
SeataDataSourceProxy proxy = (SeataDataSourceProxy) bean;
DataSource origin = proxy.getTargetDataSource();
Object originEnhancer = super.wrapIfNecessary(origin, beanName, cacheKey);
// this mean origin is either excluded by user or had been proxy before
if (origin == originEnhancer) {
return origin;
}
// else, put <origin, proxy> to holder and return originEnhancer
DataSourceProxyHolder.put(origin, proxy);
return originEnhancer;
}
SeataDataSourceProxy buildProxy(DataSource origin, String proxyMode) {
if (BranchType.AT.name().equalsIgnoreCase(proxyMode)) {
return new DataSourceProxy(origin);
}
if (BranchType.XA.name().equalsIgnoreCase(proxyMode)) {
return new DataSourceProxyXA(origin);
}
throw new IllegalArgumentException("Unknown dataSourceProxyMode: " + proxyMode);
}
可以看到在spring拦截到数据源bean的时候,会去把这个原始的数据源bean通过父类的wrapIfNecessary方法进行动态代理,然后如果是AT模式会再把这个动态代理bean包装成一个DataSourceProxy,接着把原始的数据源和包装后的DataSourceProxy对应放入到DataSourceProxyHolder中,最后返回动态代理的数据源bean
数据源代理增强器-SeataAutoDataSourceProxyAdvice
在SeataAutoDataSourceProxyCreator的wrapIfNecessary方法中对原始的数据源进行动态代理后,那么具体的代理逻辑是什么呢?在AbstractAutoProxyCreator中,会通过子类重写getAdvicesAndAdvisorsForBean方法返回对应的advisor来给目标bean构造拦截链,在SeataAutoDataSourceProxyCreator中也对getAdvicesAndAdvisorsForBean方法进行了重写
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) {
return advisors;
}
public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes, String dataSourceProxyMode) {
setProxyTargetClass(!useJdkProxy);
this.excludes = new HashSet<>(Arrays.asList(excludes));
this.dataSourceProxyMode = dataSourceProxyMode;
this.advisors = buildAdvisors(dataSourceProxyMode);
}
private Object[] buildAdvisors(String dataSourceProxyMode) {
Advice advice = new SeataAutoDataSourceProxyAdvice(dataSourceProxyMode);
return new Object[]{new DefaultIntroductionAdvisor(advice)};
}
可以看到在SeataAutoDataSourceProxyCreator被创建的时候就会通过buildAdvisors方法创建一个advisor,这个advisor的advice则是SeataAutoDataSourceProxyAdvice,然后在重写getAdvicesAndAdvisorsForBean的时候直接把这个advisor进行返回,这就意味着调用生成的数据源代理会走SeataAutoDataSourceProxyAdvice这个增强器的增强逻辑,增强代码如下:io.seata.spring.annotation.datasource.SeataAutoDataSourceProxyAdvice#invoke
public Object invoke(MethodInvocation invocation) throws Throwable {
// 是否需要调用DataSourceProxy的方法
if (!inExpectedContext()) {
// 如果不需要,则调用原始数据源的方法
return invocation.proceed();
}
// 代码来到这里说明需要调用DataSourceProxy的方法,比如getConnection,返回的就是ConnectionProxy
Method method = invocation.getMethod();
String name = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
Method declared;
try {
declared = DataSource.class.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
// mean this method is not declared by DataSource
return invocation.proceed();
}
// switch invoke instance to its proxy
DataSource origin = (DataSource) invocation.getThis();
SeataDataSourceProxy proxy = DataSourceProxyHolder.get(origin);
Object[] args = invocation.getArguments();
return declared.invoke(proxy, args);
}
也就是说我们之后调用代理数据源方法的时候,都会去走到这个SeataAutoDataSourceProxyAdvice的invoke方法中。一开始会有一个判断,这个判断非常重要,也是我们解决这篇文章开始说的问题的关键
boolean inExpectedContext() {
if (RootContext.requireGlobalLock()) {
return true;
}
if (!RootContext.inGlobalTransaction()) {
return false;
}
return branchType == RootContext.getBranchType();
}
这个判断方法很简单,就是判断一下是否加了@GlobalLock注解,或者当前的方法调用上下文是否处于全局事务中,最后再看分支类型是否与指定的一致。重点我们看第二个判断,我们知道seata在多个服务进行串联全局事务的时候是通过在请求中传递xid实现的,那么如果我们这次调用并不是在一个全局事务中,io.seata.core.context.RootContext#inGlobalTransaction方法就会返回false(该方法里面就是判断线程上下文中是否存在xid),最终inExpectedContext方法就会返回false,一旦返回false,invoke方法中就会直接执行invocation.proceed(),这行代码的作用就是直接执行下一个拦截器,但是我们也知道数据源代理只有SeataAutoDataSourceProxyAdvice这一个拦截器,当没有拦截器的时候就会执行原生数据源的方法。但是如果当前是在全局事务中呢?此时inExpectedContext方法就会返回true,此时就会执行下面的代码逻辑了
// 代码来到这里说明需要调用DataSourceProxy的方法,比如getConnection,返回的就是ConnectionProxy
Method method = invocation.getMethod();
String name = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
Method declared;
try {
declared = DataSource.class.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
// mean this method is not declared by DataSource
return invocation.proceed();
}
// switch invoke instance to its proxy
DataSource origin = (DataSource) invocation.getThis();
SeataDataSourceProxy proxy = DataSourceProxyHolder.get(origin);
Object[] args = invocation.getArguments();
return declared.invoke(proxy, args);
此时会从动态代理的数据源bean中获取到原始的数据源对象,再根据原始数据源去DataSourceProxyHolder中找到对应的DataSourceProxy,然后执行DataSourceProxy对象对应的方法
总结
引入seata-spring-boot-starter包之后,seata会通过springboot的自动配置给容器注入一个SeataAutoDataSourceProxyCreator,该组件本质上是一个BeanPostProcessor,在服务启动的时候会去拦截数据源bean,然后对这个bean生成一个动态代理bean,之后每一次调用数据源bean的方法的时候会进行代理增强,增强的逻辑就是会去检查当前的调用上下文是否处于一个全局事务中,或者是否加了@GlobalLock注解,如果是则调用seata的数据源包装对象DataSourceProxy对应的方法,如果不是,则放行调用原始的数据源方法