Java极客 | 作者 / 铿然一叶 这是Java极客的第 100 篇原创文章
相关阅读:
萌新快速成长之路
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
如何编写软件设计文档
Seata源码(一)初始化
Seata源码(二)事务基础对象
Seata源码(三)事务处理类结构和流程
Seata源码(四)全局锁GlobalLock
Seata源码(五)Seata数据库操作
Seata源码(六)Seata的undo日志操作
Seata源码(七)Seata事务故障处理
Seata源码(八)Seata事务生命周期hook\
兄弟姐妹们,帮忙点赞、评论、收藏三连,各位的支持更有快速连更的动力,多谢了!!!
兄弟姐妹们,帮忙点赞、评论、收藏三连,各位的支持更有快速连更的动力,多谢了!!!
兄弟姐妹们,帮忙点赞、评论、收藏三连,各位的支持更有快速连更的动力,多谢了!!!
1. 核心类
TCC相关的核心类结构如下:
类 | 描述 |
---|---|
LocalTCC | 本地TCC注解,类级别,如果不使用dubbo,sofa来创建bean,则需要LocalTCC注解 |
TwoPhaseBusinessAction | 两阶段业务动作注解,注解哪些方法是prepare、commit、rollback方法,方法级别 |
BusinessActionContext | 业务上下文,用于传递上下文信息 |
BusinessActionContextParameter | 业务上下文参数注解,用于将prepare方法使用的参数传递给commit和rollback方法,方法参数级别 |
TCCResource | TCC资源,存放业务类的元数据,名称,方法和参数 |
TCCResourceManager | TCC资源管理者,注册TCC资源到缓存中,根据TCC资源获取业务方法元数据并通过反射来调用。 |
TccActionInterceptor | TCC的prepare方法拦截器 |
ActionInterceptorHandler | 方法拦截处理者,初始化上下文,注册branch信息 |
GlobalTransactionScanner | 全局事务扫描者,根据TCC注解添加TCC拦截器 |
TCCBeanParserUtils | TCC工具类 |
2. 源码
2.1 添加TCC拦截器
2.1.1 入口
GlobalTransactionScanner.java
java
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
try {
synchronized (PROXYED_SET) {
if (PROXYED_SET.contains(beanName)) {
return bean;
}
interceptor = null;
//check TCC proxy
if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
//TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC
interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
(ConfigurationChangeListener)interceptor);
2.1.2 判断是否添加拦截器
这段逻辑挺多,判断LocalTCC,TwoPhaseBusinessAction注解,以及是否远程bean:
TCCBeanParserUtils.java
java
public static boolean isTccAutoProxy(Object bean, String beanName, ApplicationContext applicationContext) {
boolean isRemotingBean = parserRemotingServiceInfo(bean, beanName);
//get RemotingBean description
RemotingDesc remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(beanName);
//is remoting bean
if (isRemotingBean) {
if (remotingDesc != null && remotingDesc.getProtocol() == Protocols.IN_JVM) {
//LocalTCC
return isTccProxyTargetBean(remotingDesc);
} else {
// sofa:reference / dubbo:reference, factory bean
return false;
}
} else {
if (remotingDesc == null) {
//check FactoryBean
if (isRemotingFactoryBean(bean, beanName, applicationContext)) {
remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(beanName);
return isTccProxyTargetBean(remotingDesc);
} else {
return false;
}
} else {
return isTccProxyTargetBean(remotingDesc);
}
}
}
2.2 拦截器调用业务方法
入口:
TccActionInterceptor.java
java
public Object invoke(final MethodInvocation invocation) throws Throwable {
if (!RootContext.inGlobalTransaction() || disable || RootContext.inSagaBranch()) {
//not in transaction
return invocation.proceed();
}
Method method = getActionInterfaceMethod(invocation);
TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class);
//try method
if (businessAction != null) {
//save the xid
String xid = RootContext.getXID();
//save the previous branchType
BranchType previousBranchType = RootContext.getBranchType();
//if not TCC, bind TCC branchType
if (BranchType.TCC != previousBranchType) {
RootContext.bindBranchType(BranchType.TCC);
}
try {
Object[] methodArgs = invocation.getArguments();
//Handler the TCC Aspect
Map<String, Object> ret = actionInterceptorHandler.proceed(method, methodArgs, xid, businessAction,
invocation::proceed);
//return the final result
return ret.get(Constants.TCC_METHOD_RESULT);
}
finally {
//if not TCC, unbind branchType
if (BranchType.TCC != previousBranchType) {
RootContext.unbindBranchType();
}
//MDC remove branchId
MDC.remove(RootContext.MDC_KEY_BRANCH_ID);
}
}
return invocation.proceed();
}
通过ActionInterceptorHandler调用业务方法:
ActionInterceptorHandler.java
java
public Map<String, Object> proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction,
Callback<Object> targetCallback) throws Throwable {
Map<String, Object> ret = new HashMap<>(4);
//TCC name
String actionName = businessAction.name();
BusinessActionContext actionContext = new BusinessActionContext();
actionContext.setXid(xid);
//set action name
actionContext.setActionName(actionName);
//Creating Branch Record
String branchId = doTccActionLogStore(method, arguments, businessAction, actionContext);
actionContext.setBranchId(branchId);
//MDC put branchId
MDC.put(RootContext.MDC_KEY_BRANCH_ID, branchId);
//set the parameter whose type is BusinessActionContext
Class<?>[] types = method.getParameterTypes();
int argIndex = 0;
for (Class<?> cls : types) {
if (cls.getName().equals(BusinessActionContext.class.getName())) {
arguments[argIndex] = actionContext;
break;
}
argIndex++;
}
//the final parameters of the try method
ret.put(Constants.TCC_METHOD_ARGUMENTS, arguments);
//the final result
ret.put(Constants.TCC_METHOD_RESULT, targetCallback.execute());
return ret;
}
2.3 TwoPhaseBusinessAction注解
2.3.1 作用
在prepare方法上添加此注解,指定对应的commit和rollback方法。
2.3.2 例子
java
@LocalTCC
public interface TccActionOne {
@TwoPhaseBusinessAction(name = "TccActionOne", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext, int a);
public boolean commit(BusinessActionContext actionContext);
public boolean rollback(BusinessActionContext actionContext);
}
2.3.3 TCC资源注册
TCC资源包含业务bean的信息,需要先注册到资源管理器中,在触发commit和rollback时从资源管理器中取出来,通过反射方法调用。
资源注册的入口在TCCBeanParserUtils,最终调用了TCCResourceManager。
DefaultRemotingParser解析TwoPhaseBusinessAction注解获取资源信息,接着调用DefaultResourceManager来注册资源:
DefaultRemotingParser.java
java
public RemotingDesc parserRemotingServiceInfo(Object bean, String beanName, RemotingParser remotingParser) {
RemotingDesc remotingBeanDesc = remotingParser.getServiceDesc(bean, beanName);
if (remotingBeanDesc == null) {
return null;
}
remotingServiceMap.put(beanName, remotingBeanDesc);
Class<?> interfaceClass = remotingBeanDesc.getInterfaceClass();
Method[] methods = interfaceClass.getMethods();
if (remotingParser.isService(bean, beanName)) {
try {
//service bean, registry resource
Object targetBean = remotingBeanDesc.getTargetBean();
for (Method m : methods) {
TwoPhaseBusinessAction twoPhaseBusinessAction = m.getAnnotation(TwoPhaseBusinessAction.class);
if (twoPhaseBusinessAction != null) {
TCCResource tccResource = new TCCResource();
tccResource.setActionName(twoPhaseBusinessAction.name());
tccResource.setTargetBean(targetBean);
tccResource.setPrepareMethod(m);
tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod());
tccResource.setCommitMethod(ReflectionUtil
.getMethod(interfaceClass, twoPhaseBusinessAction.commitMethod(),
new Class[] {BusinessActionContext.class}));
tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod());
tccResource.setRollbackMethod(ReflectionUtil
.getMethod(interfaceClass, twoPhaseBusinessAction.rollbackMethod(),
new Class[] {BusinessActionContext.class}));
//registry tcc resource
DefaultResourceManager.get().registerResource(tccResource);
}
}
} catch (Throwable t) {
throw new FrameworkException(t, "parser remoting service error");
}
}
DefaultResourceManager根据事务分支类型获取到对应的资源管理器,TCC对应TCCResourceManager:
java
public void registerResource(Resource resource) {
getResourceManager(resource.getBranchType()).registerResource(resource);
}
最后放到TCC资源管理器map缓存中:
TCCResourceManager.java
java
private Map<String, Resource> tccResourceCache = new ConcurrentHashMap<>();
public void registerResource(Resource resource) {
TCCResource tccResource = (TCCResource)resource;
tccResourceCache.put(tccResource.getResourceId(), tccResource);
super.registerResource(tccResource);
}
2.3.3 commit/rollback方法调用
核心逻辑是TCCResourceManager根据要处理的resourceId从缓存获取对应的TCCResource,然后根据TCCResource记录的bean信息,通过反射调用对应的方法:
commit方法:
java
public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId);
if (tccResource == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not exist, resourceId: %s", resourceId));
}
Object targetTCCBean = tccResource.getTargetBean();
Method commitMethod = tccResource.getCommitMethod();
if (targetTCCBean == null || commitMethod == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not available, resourceId: %s", resourceId));
}
try {
//BusinessActionContext
BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
applicationData);
Object ret = commitMethod.invoke(targetTCCBean, businessActionContext);
LOGGER.info("TCC resource commit result : {}, xid: {}, branchId: {}, resourceId: {}", ret, xid, branchId, resourceId);
boolean result;
if (ret != null) {
if (ret instanceof TwoPhaseResult) {
result = ((TwoPhaseResult)ret).isSuccess();
} else {
result = (boolean)ret;
}
} else {
result = true;
}
return result ? BranchStatus.PhaseTwo_Committed : BranchStatus.PhaseTwo_CommitFailed_Retryable;
} catch (Throwable t) {
String msg = String.format("commit TCC resource error, resourceId: %s, xid: %s.", resourceId, xid);
LOGGER.error(msg, t);
return BranchStatus.PhaseTwo_CommitFailed_Retryable;
}
}
rollback方法:
java
public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId);
if (tccResource == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not exist, resourceId: %s", resourceId));
}
Object targetTCCBean = tccResource.getTargetBean();
Method rollbackMethod = tccResource.getRollbackMethod();
if (targetTCCBean == null || rollbackMethod == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not available, resourceId: %s", resourceId));
}
try {
//BusinessActionContext
BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
applicationData);
Object ret = rollbackMethod.invoke(targetTCCBean, businessActionContext);
LOGGER.info("TCC resource rollback result : {}, xid: {}, branchId: {}, resourceId: {}", ret, xid, branchId, resourceId);
boolean result;
if (ret != null) {
if (ret instanceof TwoPhaseResult) {
result = ((TwoPhaseResult)ret).isSuccess();
} else {
result = (boolean)ret;
}
} else {
result = true;
}
return result ? BranchStatus.PhaseTwo_Rollbacked : BranchStatus.PhaseTwo_RollbackFailed_Retryable;
} catch (Throwable t) {
String msg = String.format("rollback TCC resource error, resourceId: %s, xid: %s.", resourceId, xid);
LOGGER.error(msg, t);
return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
}
}
2.4 BusinessActionContext参数
2.4.1 作用
记录上下文信息,同时可以传递给commit和rollback方法。
2.4.2 BusinessActionContext注册
上下文信息要先注册到TC,使用时下发:
ActionInterceptorHandler.java
java
protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhaseBusinessAction businessAction,
BusinessActionContext actionContext) {
String actionName = actionContext.getActionName();
String xid = actionContext.getXid();
//
Map<String, Object> context = fetchActionRequestContext(method, arguments);
context.put(Constants.ACTION_START_TIME, System.currentTimeMillis());
//init business context
initBusinessContext(context, method, businessAction);
//Init running environment context
initFrameworkContext(context);
actionContext.setActionContext(context);
//init applicationData
Map<String, Object> applicationContext = new HashMap<>(4);
applicationContext.put(Constants.TCC_ACTION_CONTEXT, context);
String applicationContextStr = JSON.toJSONString(applicationContext);
try {
//registry branch record,包括上下文参数
Long branchId = DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid,
applicationContextStr, null);
最终注册到TC:
AbstractResourceManager.java
java
public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException {
try {
BranchRegisterRequest request = new BranchRegisterRequest();
request.setXid(xid);
request.setLockKey(lockKeys);
request.setResourceId(resourceId);
request.setBranchType(branchType);
request.setApplicationData(applicationData);
BranchRegisterResponse response = (BranchRegisterResponse) RmNettyRemotingClient.getInstance().sendSyncRequest(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg()));
}
return response.getBranchId();
} catch (TimeoutException toe) {
throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe);
} catch (RuntimeException rex) {
throw new RmTransactionException(TransactionExceptionCode.BranchRegisterFailed, "Runtime", rex);
}
}
2.4.3 使用
commit或rollback方法中获取并使用:
TCCResourceManager.java
java
public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
......
try {
//BusinessActionContext
BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
applicationData);
Object ret = commitMethod.invoke(targetTCCBean, businessActionContext);
BusinessActionContext的数据来自输入参数applicationData,TC下发:
java
protected BusinessActionContext getBusinessActionContext(String xid, long branchId, String resourceId,
String applicationData) {
//transfer tcc applicationData to Context
Map tccContext = StringUtils.isBlank(applicationData) ? new HashMap() : (Map)JSON.parse(applicationData);
Map actionContextMap = (Map)tccContext.get(Constants.TCC_ACTION_CONTEXT);
BusinessActionContext businessActionContext = new BusinessActionContext(
xid, String.valueOf(branchId), actionContextMap);
businessActionContext.setActionName(resourceId);
return businessActionContext;
}
2.5 BusinessActionContextParameter注解
2.5.1 作用
BusinessActionContextParameter的作用是将prepare方法的业务参数传递给commit和rollback方法。
2.5.2 例子
java
@TwoPhaseBusinessAction(name = "DubboTccActionTwo", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext,
@BusinessActionContextParameter(paramName = "b") String b,
@BusinessActionContextParameter(paramName = "c", index = 1) List list);
这些参数会存储到一个map中,paramName为map的key。
2.5.3 参数解析
ActionInterceptorHandler.java
java
protected Map<String, Object> fetchActionRequestContext(Method method, Object[] arguments) {
Map<String, Object> context = new HashMap<>(8);
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
for (int j = 0; j < parameterAnnotations[i].length; j++) {
if (parameterAnnotations[i][j] instanceof BusinessActionContextParameter) {
BusinessActionContextParameter param = (BusinessActionContextParameter)parameterAnnotations[i][j];
if (arguments[i] == null) {
throw new IllegalArgumentException("@BusinessActionContextParameter 's params can not null");
}
Object paramObject = arguments[i];
int index = param.index();
//List, get by index
if (index >= 0) {
@SuppressWarnings("unchecked")
Object targetParam = ((List<Object>)paramObject).get(index);
if (param.isParamInProperty()) {
context.putAll(ActionContextUtil.fetchContextFromObject(targetParam));
} else {
context.put(param.paramName(), targetParam);
}
} else {
if (param.isParamInProperty()) {
context.putAll(ActionContextUtil.fetchContextFromObject(paramObject));
} else {
context.put(param.paramName(), paramObject);
}
}
}
}
}
return context;
}
2.5.4 参数注册
BusinessActionContextParameter注解的参数和BusinessActionContext参数一起注册到TC,使用时下发(详情可参考前面BusinessActionContext的注册逻辑):
ActionInterceptorHandler.java
java
protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhaseBusinessAction businessAction,
BusinessActionContext actionContext) {
String actionName = actionContext.getActionName();
String xid = actionContext.getXid();
// 这里获取BusinessActionContextParameter注解的参数
Map<String, Object> context = fetchActionRequestContext(method, arguments);
context.put(Constants.ACTION_START_TIME, System.currentTimeMillis());
//init business context
initBusinessContext(context, method, businessAction);
//Init running environment context
initFrameworkContext(context);
actionContext.setActionContext(context);
//init applicationData
Map<String, Object> applicationContext = new HashMap<>(4);
// 存入map的key为Constants.TCC_ACTION_CONTEXT
applicationContext.put(Constants.TCC_ACTION_CONTEXT, context);
String applicationContextStr = JSON.toJSONString(applicationContext);
2.5.5 参数使用
参数传递和BusinessActionContext一样,参考前面BusinessActionContext参数的例子。
具体使用时通过传入的BusinessActionContext参数,调用其getActionContext方法并按照在接口方法参数上BusinessActionContextParameter注解定义的paramName获取:
java
public boolean commit(BusinessActionContext actionContext) {
String xid = actionContext.getXid();
System.out.println(
"TccActionTwo commit, xid:" + xid + ", b:" + actionContext.getActionContext("b") + ", c:" + actionContext
.getActionContext("c"));
ResultHolder.setActionTwoResult(xid, "T");
return true;
}
兄弟姐妹们,帮忙点赞、评论、收藏三连,各位的支持更有快速连更的动力,多谢了!!!
兄弟姐妹们,帮忙点赞、评论、收藏三连,各位的支持更有快速连更的动力,多谢了!!!
兄弟姐妹们,帮忙点赞、评论、收藏三连,各位的支持更有快速连更的动力,多谢了!!!
end.