chore(project): 添加项目配置文件和忽略规则

- 添加 Babel 配置文件支持 ES6+ 语法转换
- 添加 ESLint 忽略规则和配置文件
- 添加 Git 忽略规则文件
- 添加 Travis CI 配置文件
- 添加 1.4.2 版本变更日志文件
- 添加 Helm 图表辅助模板文件
- 添加 Helm 忽略规则文件
This commit is contained in:
2026-03-27 17:36:48 +08:00
commit c2453d6434
1703 changed files with 277582 additions and 0 deletions

69
tcc/README.MD Normal file
View File

@@ -0,0 +1,69 @@
### TCC
#### TCC 简介
在2PC两阶段提交协议中事务管理器分两阶段协调资源管理资源管理器对外提供了3个操作分别是一阶段的准备操作二阶段的提交操作和回滚操作
TCC服务作为一种事务资源遵循两阶段提交协议由业务层面自定义需要用户根据业务逻辑编码实现其包含Try、Confirm 和 Cancel 3个操作其中Try操作对应分布式事务一阶段的准备Confirm操作对应分布式事务二阶段提交Cancel对应分布式事务二阶段回滚
- Try
资源的检查和预留;
- Confirm
使用预留的资源完成真正的业务操作要求Try成功Confirm 一定要能成功;
- Cancel
释放预留资源;
TCC的3个方法均由用户根据业务场景编码实现并对外发布成微服务供事务管理器调用事务管理器在一阶段调用TCC的Try方法在二阶段提交时调用Confirm方法在二阶段回滚时调用Cancel方法。
#### TCC实现
##### 1、TCC微服务
TCC服务由用户编码实现并对外发布成微服务目前支持3种形式的TCC微服务分别是
- SofaRpc 服务
用户将实现的TCC操作对外发布成 SofaRpc 服务事务管理器通过订阅SofaRpc服务来协调TCC资源
- Dubbo 服务
将TCC发布成dubbo服务事务管理器订阅dubbo服务来协调TCC资源
- Local TCC
本地普通的TCC Bean非远程服务事务管理器通过本地方法调用来协调TCC 资源;
##### 2、TCC资源动态代理
对TCC服务进行动态代理GlobalTransactionScanner中当扫描到TCC 服务的 reference会对其进行动态代理
TCC 动态代理的主要功能是生成TCC运行时上下文、透传业务参数、注册分支事务记录
#### 模块说明
seata-tcc 包含TCC主要代码
- interceptorTCC 动态代理;
- remotingTCC微服务扫描、RPC协议解析、TCC资源注册
- TccResourceManager、RMHandlerTCC TCC资源管理器
#### 其他说明:
本此为了区分AT、TCC 2种资源类型在客户端和服务端通信的接口中均添加了 BranchType 参数;

49
tcc/pom.xml Normal file
View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 1999-2019 Seata.io Group.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.seata</groupId>
<artifactId>seata-parent</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-tcc</artifactId>
<packaging>jar</packaging>
<name>seata-tcc ${project.version}</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seata-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>seata-rm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,46 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc;
import io.seata.core.model.BranchType;
import io.seata.core.model.ResourceManager;
import io.seata.core.protocol.transaction.UndoLogDeleteRequest;
import io.seata.rm.AbstractRMHandler;
import io.seata.rm.DefaultResourceManager;
/**
* The type Rm handler tcc.
*
* @author zhangsen
*/
public class RMHandlerTCC extends AbstractRMHandler {
@Override
public void handle(UndoLogDeleteRequest request) {
//DO nothing
}
@Override
protected ResourceManager getResourceManager() {
return DefaultResourceManager.get().getResourceManager(BranchType.TCC);
}
@Override
public BranchType getBranchType() {
return BranchType.TCC;
}
}

View File

@@ -0,0 +1,228 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc;
import java.lang.reflect.Method;
import io.seata.core.model.BranchType;
import io.seata.core.model.Resource;
/**
* The type Tcc resource.
*
* @author zhangsen
*/
public class TCCResource implements Resource {
private String resourceGroupId = "DEFAULT";
private String appName;
private String actionName;
private Object targetBean;
private Method prepareMethod;
private String commitMethodName;
private Method commitMethod;
private String rollbackMethodName;
private Method rollbackMethod;
@Override
public String getResourceGroupId() {
return resourceGroupId;
}
/**
* Sets resource group id.
*
* @param resourceGroupId the resource group id
*/
public void setResourceGroupId(String resourceGroupId) {
this.resourceGroupId = resourceGroupId;
}
@Override
public String getResourceId() {
return actionName;
}
@Override
public BranchType getBranchType() {
return BranchType.TCC;
}
/**
* Gets app name.
*
* @return the app name
*/
public String getAppName() {
return appName;
}
/**
* Sets app name.
*
* @param appName the app name
*/
public void setAppName(String appName) {
this.appName = appName;
}
/**
* Gets action name.
*
* @return the action name
*/
public String getActionName() {
return actionName;
}
/**
* Sets action name.
*
* @param actionName the action name
*/
public void setActionName(String actionName) {
this.actionName = actionName;
}
/**
* Gets target bean.
*
* @return the target bean
*/
public Object getTargetBean() {
return targetBean;
}
/**
* Sets target bean.
*
* @param targetBean the target bean
*/
public void setTargetBean(Object targetBean) {
this.targetBean = targetBean;
}
/**
* Gets prepare method.
*
* @return the prepare method
*/
public Method getPrepareMethod() {
return prepareMethod;
}
/**
* Sets prepare method.
*
* @param prepareMethod the prepare method
*/
public void setPrepareMethod(Method prepareMethod) {
this.prepareMethod = prepareMethod;
}
/**
* Gets commit method.
*
* @return the commit method
*/
public Method getCommitMethod() {
return commitMethod;
}
/**
* Sets commit method.
*
* @param commitMethod the commit method
*/
public void setCommitMethod(Method commitMethod) {
this.commitMethod = commitMethod;
}
/**
* Gets rollback method.
*
* @return the rollback method
*/
public Method getRollbackMethod() {
return rollbackMethod;
}
/**
* Sets rollback method.
*
* @param rollbackMethod the rollback method
*/
public void setRollbackMethod(Method rollbackMethod) {
this.rollbackMethod = rollbackMethod;
}
/**
* Gets commit method name.
*
* @return the commit method name
*/
public String getCommitMethodName() {
return commitMethodName;
}
/**
* Sets commit method name.
*
* @param commitMethodName the commit method name
*/
public void setCommitMethodName(String commitMethodName) {
this.commitMethodName = commitMethodName;
}
/**
* Gets rollback method name.
*
* @return the rollback method name
*/
public String getRollbackMethodName() {
return rollbackMethodName;
}
/**
* Sets rollback method name.
*
* @param rollbackMethodName the rollback method name
*/
public void setRollbackMethodName(String rollbackMethodName) {
this.rollbackMethodName = rollbackMethodName;
}
@Override
public int hashCode() {
return actionName.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TCCResource)) {
return false;
}
return this.actionName.equals(((TCCResource)obj).actionName);
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.fastjson.JSON;
import io.seata.common.Constants;
import io.seata.common.exception.ShouldNeverHappenException;
import io.seata.common.util.StringUtils;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.BranchStatus;
import io.seata.core.model.BranchType;
import io.seata.core.model.Resource;
import io.seata.rm.AbstractResourceManager;
import io.seata.rm.tcc.api.BusinessActionContext;
/**
* TCC resource manager
*
* @author zhangsen
*/
public class TCCResourceManager extends AbstractResourceManager {
/**
* TCC resource cache
*/
private Map<String, Resource> tccResourceCache = new ConcurrentHashMap<>();
/**
* Instantiates a new Tcc resource manager.
*/
public TCCResourceManager() {
}
/**
* registry TCC resource
*
* @param resource The resource to be managed.
*/
@Override
public void registerResource(Resource resource) {
TCCResource tccResource = (TCCResource)resource;
tccResourceCache.put(tccResource.getResourceId(), tccResource);
super.registerResource(tccResource);
}
@Override
public Map<String, Resource> getManagedResources() {
return tccResourceCache;
}
/**
* TCC branch commit
*
* @param branchType
* @param xid Transaction id.
* @param branchId Branch id.
* @param resourceId Resource id.
* @param applicationData Application data bind with this branch.
* @return BranchStatus
* @throws TransactionException TransactionException
*/
@Override
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;
}
}
/**
* TCC branch rollback
*
* @param branchType the branch type
* @param xid Transaction id.
* @param branchId Branch id.
* @param resourceId Resource id.
* @param applicationData Application data bind with this branch.
* @return BranchStatus
* @throws TransactionException TransactionException
*/
@Override
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;
}
}
/**
* transfer tcc applicationData to BusinessActionContext
*
* @param xid the xid
* @param branchId the branch id
* @param resourceId the resource id
* @param applicationData the application data
* @return business action context
*/
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;
}
@Override
public BranchType getBranchType() {
return BranchType.TCC;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc;
import io.seata.common.util.StringUtils;
/**
* the TCC method result
*
* @author zhangsen
*/
public class TwoPhaseResult {
/**
* is Success ?
*/
private boolean isSuccess;
/**
* result message
*/
private String message;
/**
* Instantiates a new Two phase result.
*
* @param isSuccess the is success
* @param msg the msg
*/
public TwoPhaseResult(boolean isSuccess, String msg) {
this.isSuccess = isSuccess;
this.message = msg;
}
/**
* Is success boolean.
*
* @return the boolean
*/
public boolean isSuccess() {
return isSuccess;
}
/**
* Sets success.
*
* @param isSuccess the is success
*/
public void setSuccess(boolean isSuccess) {
this.isSuccess = isSuccess;
}
/**
* Gets message.
*
* @return the message
*/
public String getMessage() {
return message;
}
/**
* Sets message.
*
* @param msg the message
*/
public void setMessage(String msg) {
this.message = msg;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append("isSuccess:").append(isSuccess);
if (StringUtils.isNotBlank(message)) {
sb.append(", msg").append(message);
}
sb.append("]");
return sb.toString();
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.api;
import java.io.Serializable;
import java.util.Map;
/**
* The type Business action context.
*/
public class BusinessActionContext implements Serializable {
private static final long serialVersionUID = 6539226288677737991L;
private String xid;
private String branchId;
private String actionName;
private Map<String, Object> actionContext;
/**
* Instantiates a new Business action context.
*/
public BusinessActionContext() {
}
/**
* Instantiates a new Business action context.
*
* @param xid the xid
* @param branchId the branch id
* @param actionContext the action context
*/
public BusinessActionContext(String xid, String branchId, Map<String, Object> actionContext) {
this.xid = xid;
this.branchId = branchId;
this.setActionContext(actionContext);
}
/**
* Gets action context.
*
* @param key the key
* @return the action context
*/
public Object getActionContext(String key) {
return actionContext.get(key);
}
/**
* Gets branch id.
*
* @return the branch id
*/
public long getBranchId() {
return branchId != null ? Long.parseLong(branchId) : -1;
}
/**
* Sets branch id.
*
* @param branchId the branch id
*/
public void setBranchId(long branchId) {
this.branchId = String.valueOf(branchId);
}
/**
* Gets action context.
*
* @return the action context
*/
public Map<String, Object> getActionContext() {
return actionContext;
}
/**
* Sets action context.
*
* @param actionContext the action context
*/
public void setActionContext(Map<String, Object> actionContext) {
this.actionContext = actionContext;
}
/**
* Gets xid.
*
* @return the xid
*/
public String getXid() {
return xid;
}
/**
* Sets xid.
*
* @param xid the xid
*/
public void setXid(String xid) {
this.xid = xid;
}
/**
* Sets branch id.
*
* @param branchId the branch id
*/
public void setBranchId(String branchId) {
this.branchId = branchId;
}
/**
* Gets action name.
*
* @return the action name
*/
public String getActionName() {
return actionName;
}
/**
* Sets action name.
*
* @param actionName the action name
*/
public void setActionName(String actionName) {
this.actionName = actionName;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[xid:").append(xid)
.append(",branch_Id:").append(branchId).append(",action_name:").append(actionName)
.append(",action_context:")
.append(actionContext).append("]");
return sb.toString();
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* the TCC parameters that need to be passed to the BusinessActivityContext;
* <p>
* add this annotation on the parameters of the try method, and the parameters will be passed to the
* BusinessActivityContext
*
* @author zhangsen
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
public @interface BusinessActionContextParameter {
/**
* parameter's name
*
* @return the string
*/
String paramName() default "";
/**
* if it is a sharding param ?
*
* @return boolean boolean
*/
boolean isShardingParam() default false;
/**
* Specify the index of the parameter in the List
*
* @return int int
*/
int index() default -1;
/**
* if get the parameter from the property of the object ?
*
* @return boolean boolean
*/
boolean isParamInProperty() default false;
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.api;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import io.seata.common.Constants;
/**
* The type Business activity context.
*
* @author zhangsen
*/
public class BusinessActivityContext implements Serializable {
/** */
private static final long serialVersionUID = 6539226288677737992L;
private Map<String, Object> context = new HashMap<>();
/**
* Instantiates a new Business activity context.
*/
public BusinessActivityContext() {
}
/**
* Instantiates a new Business activity context.
*
* @param context the context
*/
public BusinessActivityContext(Map<String, Object> context) {
this.context = context;
}
/**
* Add context.
*
* @param key the key
* @param value the value
*/
public void addContext(String key, Object value) {
context.put(key, value);
}
/**
* Fetch start time long.
*
* @return the long
*/
public Long fetchStartTime() {
return (Long)context.get(Constants.START_TIME);
}
/**
* Get context object.
*
* @param key the key
* @return the object
*/
public Object getContext(String key) {
return context.get(key);
}
/**
* Gets context.
*
* @return the context
*/
public Map<String, Object> getContext() {
return context;
}
/**
* Sets context.
*
* @param context the context
*/
public void setContext(Map<String, Object> context) {
this.context = context;
}
@Override
public String toString() {
return context.toString();
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Local TCC bean annotation, add on the TCC interface
*
* @author zhangsen
* @see io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary(Object, String, Object) // the scanner for TM, GlobalLock, and TCC mode
* @see io.seata.rm.tcc.remoting.parser.LocalTCCRemotingParser // the RemotingParser impl for LocalTCC
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface LocalTCC {
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.api;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* TCC annotation.
* Define a TCC interface, which added on the try method.
* Must be used with `@LocalTCC`.
*
* @author zhangsen
* @see io.seata.rm.tcc.api.LocalTCC // TCC annotation, which added on the TCC interface. It can't be left out.
* @see io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary(Object, String, Object) // the scanner for TM, GlobalLock, and TCC mode
* @see io.seata.spring.tcc.TccActionInterceptor // the interceptor of TCC mode
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Inherited
public @interface TwoPhaseBusinessAction {
/**
* TCC bean name, must be unique
*
* @return the string
*/
String name();
/**
* commit methed name
*
* @return the string
*/
String commitMethod() default "commit";
/**
* rollback method name
*
* @return the string
*/
String rollbackMethod() default "rollback";
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.interceptor;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
/**
* The interface Action context filter.
*
* @author zhangsen
*/
public interface ActionContextFilter {
/**
* Need filter boolean.
*
* @param parameter the parameter
* @return the boolean
*/
boolean needFilter(BusinessActionContextParameter parameter);
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.interceptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.StringUtils;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
/**
* Extracting TCC Context from Method
*
* @author zhangsen
*/
public class ActionContextUtil {
private ActionContextUtil() {
}
/**
* Extracting context data from parameters
*
* @param targetParam the target param
* @return map map
*/
public static Map<String, Object> fetchContextFromObject(Object targetParam) {
try {
Map<String, Object> context = new HashMap<>(8);
List<Field> fields = new ArrayList<>();
getAllField(targetParam.getClass(), fields);
for (Field f : fields) {
String fieldName = f.getName();
BusinessActionContextParameter annotation = f.getAnnotation(BusinessActionContextParameter.class);
if (annotation != null) {
f.setAccessible(true);
Object paramObject = f.get(targetParam);
int index = annotation.index();
if (annotation.isParamInProperty()) {
if (index >= 0) {
@SuppressWarnings("unchecked")
Object targetObject = ((List<Object>) paramObject).get(index);
context.putAll(fetchContextFromObject(targetObject));
} else {
context.putAll(fetchContextFromObject(paramObject));
}
} else {
if (StringUtils.isBlank(annotation.paramName())) {
context.put(fieldName, paramObject);
} else {
context.put(annotation.paramName(), paramObject);
}
}
}
}
return context;
} catch (Throwable t) {
throw new FrameworkException(t, "fetchContextFromObject failover");
}
}
/**
* Gets all field.
*
* @param interFace the inter face
* @param fields the fields
*/
public static void getAllField(Class<?> interFace, List<Field> fields) {
if (interFace == Object.class || interFace.isInterface()) {
return;
}
Field[] field = interFace.getDeclaredFields();
fields.addAll(Arrays.asList(field));
getAllField(interFace.getSuperclass(), fields);
}
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.interceptor;
import com.alibaba.fastjson.JSON;
import io.seata.common.Constants;
import io.seata.common.exception.FrameworkException;
import io.seata.common.executor.Callback;
import io.seata.common.util.NetUtil;
import io.seata.core.context.RootContext;
import io.seata.core.model.BranchType;
import io.seata.rm.DefaultResourceManager;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Handler the TCC Participant Aspect : Setting Context, Creating Branch Record
*
* @author zhangsen
*/
public class ActionInterceptorHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ActionInterceptorHandler.class);
/**
* Handler the TCC Aspect
*
* @param method the method
* @param arguments the arguments
* @param businessAction the business action
* @param targetCallback the target callback
* @return map map
* @throws Throwable the throwable
*/
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;
}
/**
* Creating Branch Record
*
* @param method the method
* @param arguments the arguments
* @param businessAction the business action
* @param actionContext the action context
* @return the string
*/
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);
return String.valueOf(branchId);
} catch (Throwable t) {
String msg = String.format("TCC branch Register error, xid: %s", xid);
LOGGER.error(msg, t);
throw new FrameworkException(t, msg);
}
}
/**
* Init running environment context
*
* @param context the context
*/
protected void initFrameworkContext(Map<String, Object> context) {
try {
context.put(Constants.HOST_NAME, NetUtil.getLocalIp());
} catch (Throwable t) {
LOGGER.warn("getLocalIP error", t);
}
}
/**
* Init business context
*
* @param context the context
* @param method the method
* @param businessAction the business action
*/
protected void initBusinessContext(Map<String, Object> context, Method method,
TwoPhaseBusinessAction businessAction) {
if (method != null) {
//the phase one method name
context.put(Constants.PREPARE_METHOD, method.getName());
}
if (businessAction != null) {
//the phase two method name
context.put(Constants.COMMIT_METHOD, businessAction.commitMethod());
context.put(Constants.ROLLBACK_METHOD, businessAction.rollbackMethod());
context.put(Constants.ACTION_NAME, businessAction.name());
}
}
/**
* Extracting context data from parameters, add them to the context
*
* @param method the method
* @param arguments the arguments
* @return map map
*/
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;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting;
/**
* remoting protocols enum
*
* @author zhangsen
*/
public interface Protocols {
/**
* sofa-rpc service
*/
short SOFA_RPC = 2;
/**
* dubbo service
*/
short DUBBO = 3;
/**
* restful service
*/
short RESTFUL = 4;
/**
* local bean
*/
short IN_JVM = 5;
/**
* hsf service
*/
short HSF = 8;
}

View File

@@ -0,0 +1,185 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting;
/**
* remoting bean info
*
* @author zhangsen
*/
public class RemotingDesc {
/**
* is referenc bean ?
*/
private boolean isReference = false;
/**
* rpc target bean, the service bean has this property
*/
private Object targetBean;
/**
* the tcc interface tyep
*/
private Class<?> interfaceClass;
/**
* interface class name
*/
private String interfaceClassName;
/**
* rpc uniqueId: hsf, dubbo's version, sofa-rpc's uniqueId
*/
private String uniqueId;
/**
* dubbo/hsf 's group
*/
private String group;
/**
* protocol: sofa-rpc, dubbo, injvm etc.
*/
private short protocol;
/**
* Gets target bean.
*
* @return the target bean
*/
public Object getTargetBean() {
return targetBean;
}
/**
* Sets target bean.
*
* @param targetBean the target bean
*/
public void setTargetBean(Object targetBean) {
this.targetBean = targetBean;
}
/**
* Gets interface class.
*
* @return the interface class
*/
public Class<?> getInterfaceClass() {
return interfaceClass;
}
/**
* Sets interface class.
*
* @param interfaceClass the interface class
*/
public void setInterfaceClass(Class<?> interfaceClass) {
this.interfaceClass = interfaceClass;
}
/**
* Gets interface class name.
*
* @return the interface class name
*/
public String getInterfaceClassName() {
return interfaceClassName;
}
/**
* Sets interface class name.
*
* @param interfaceClassName the interface class name
*/
public void setInterfaceClassName(String interfaceClassName) {
this.interfaceClassName = interfaceClassName;
}
/**
* Gets unique id.
*
* @return the unique id
*/
public String getUniqueId() {
return uniqueId;
}
/**
* Sets unique id.
*
* @param uniqueId the unique id
*/
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
/**
* Gets group.
*
* @return the group
*/
public String getGroup() {
return group;
}
/**
* Sets group.
*
* @param group the group
*/
public void setGroup(String group) {
this.group = group;
}
/**
* Gets protocol.
*
* @return the protocol
*/
public short getProtocol() {
return protocol;
}
/**
* Sets protocol.
*
* @param protocol the protocol
*/
public void setProtocol(short protocol) {
this.protocol = protocol;
}
/**
* Is reference boolean.
*
* @return the boolean
*/
public boolean isReference() {
return isReference;
}
/**
* Sets reference.
*
* @param reference the reference
*/
public void setReference(boolean reference) {
isReference = reference;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting;
import io.seata.common.exception.FrameworkException;
/**
* extract remoting bean info
*
* @author zhangsen
*/
public interface RemotingParser {
/**
* if it is remoting bean ?
*
* @param bean the bean
* @param beanName the bean name
* @return boolean boolean
* @throws FrameworkException the framework exception
*/
boolean isRemoting(Object bean, String beanName) throws FrameworkException;
/**
* if it is reference bean ?
*
* @param bean the bean
* @param beanName the bean name
* @return boolean boolean
* @throws FrameworkException the framework exception
*/
boolean isReference(Object bean, String beanName) throws FrameworkException;
/**
* if it is service bean ?
*
* @param bean the bean
* @param beanName the bean name
* @return boolean boolean
* @throws FrameworkException the framework exception
*/
boolean isService(Object bean, String beanName) throws FrameworkException;
/**
* get the remoting bean info
*
* @param bean the bean
* @param beanName the bean name
* @return service desc
* @throws FrameworkException the framework exception
*/
RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException;
/**
* the remoting protocol
*
* @return protocol
*/
short getProtocol();
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting.parser;
import io.seata.common.exception.FrameworkException;
import io.seata.rm.tcc.remoting.RemotingParser;
/**
* The type Abstracted remoting parser.
*
* @author zhangsen
*/
public abstract class AbstractedRemotingParser implements RemotingParser {
@Override
public boolean isRemoting(Object bean, String beanName) throws FrameworkException {
return isReference(bean, beanName) || isService(bean, beanName);
}
}

View File

@@ -0,0 +1,216 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting.parser;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.seata.common.exception.FrameworkException;
import io.seata.common.loader.EnhancedServiceLoader;
import io.seata.common.util.CollectionUtils;
import io.seata.common.util.ReflectionUtil;
import io.seata.rm.DefaultResourceManager;
import io.seata.rm.tcc.TCCResource;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
import io.seata.rm.tcc.remoting.RemotingDesc;
import io.seata.rm.tcc.remoting.RemotingParser;
/**
* parsing remoting bean
*
* @author zhangsen
*/
public class DefaultRemotingParser {
/**
* all remoting bean parser
*/
protected static List<RemotingParser> allRemotingParsers = new ArrayList<>();
/**
* all remoting beans beanName -> RemotingDesc
*/
protected static Map<String, RemotingDesc> remotingServiceMap = new ConcurrentHashMap<>();
private static class SingletonHolder {
private static final DefaultRemotingParser INSTANCE = new DefaultRemotingParser();
}
/**
* Get resource manager.
*
* @return the resource manager
*/
public static DefaultRemotingParser get() {
return DefaultRemotingParser.SingletonHolder.INSTANCE;
}
/**
* Instantiates a new Default remoting parser.
*/
protected DefaultRemotingParser() {
initRemotingParser();
}
/**
* init parsers
*/
protected void initRemotingParser() {
//init all resource managers
List<RemotingParser> remotingParsers = EnhancedServiceLoader.loadAll(RemotingParser.class);
if (CollectionUtils.isNotEmpty(remotingParsers)) {
allRemotingParsers.addAll(remotingParsers);
}
}
/**
* is remoting bean ?
*
* @param bean the bean
* @param beanName the bean name
* @return boolean boolean
*/
public RemotingParser isRemoting(Object bean, String beanName) {
for (RemotingParser remotingParser : allRemotingParsers) {
if (remotingParser.isRemoting(bean, beanName)) {
return remotingParser;
}
}
return null;
}
/**
* is reference bean?
*
* @param bean the bean
* @param beanName the bean name
* @return boolean boolean
*/
public boolean isReference(Object bean, String beanName) {
for (RemotingParser remotingParser : allRemotingParsers) {
if (remotingParser.isReference(bean, beanName)) {
return true;
}
}
return false;
}
/**
* is service bean ?
*
* @param bean the bean
* @param beanName the bean name
* @return boolean boolean
*/
public boolean isService(Object bean, String beanName) {
for (RemotingParser remotingParser : allRemotingParsers) {
if (remotingParser.isService(bean, beanName)) {
return true;
}
}
return false;
}
/**
* get the remoting Service desc
*
* @param bean the bean
* @param beanName the bean name
* @return service desc
*/
public RemotingDesc getServiceDesc(Object bean, String beanName) {
List<RemotingDesc> ret = new ArrayList<>();
for (RemotingParser remotingParser : allRemotingParsers) {
RemotingDesc s = remotingParser.getServiceDesc(bean, beanName);
if (s != null) {
ret.add(s);
}
}
if (ret.size() == 1) {
return ret.get(0);
} else if (ret.size() > 1) {
throw new FrameworkException(String.format("More than one RemotingParser for bean: %s", beanName));
} else {
return null;
}
}
/**
* parse the remoting bean info
*
* @param bean the bean
* @param beanName the bean name
* @param remotingParser the remoting parser
* @return remoting desc
*/
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");
}
}
if (remotingParser.isReference(bean, beanName)) {
//reference bean, TCC proxy
remotingBeanDesc.setReference(true);
}
return remotingBeanDesc;
}
/**
* Get remoting bean desc remoting desc.
*
* @param beanName the bean name
* @return the remoting desc
*/
public RemotingDesc getRemotingBeanDesc(String beanName) {
return remotingServiceMap.get(beanName);
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting.parser;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.ReflectionUtil;
import io.seata.rm.tcc.remoting.Protocols;
import io.seata.rm.tcc.remoting.RemotingDesc;
/**
* dubbo remoting bean parsing
*
* @author zhangsen
*/
public class DubboRemotingParser extends AbstractedRemotingParser {
@Override
public boolean isReference(Object bean, String beanName) throws FrameworkException {
Class<?> c = bean.getClass();
return "com.alibaba.dubbo.config.spring.ReferenceBean".equals(c.getName())
|| "org.apache.dubbo.config.spring.ReferenceBean".equals(c.getName());
}
@Override
public boolean isService(Object bean, String beanName) throws FrameworkException {
Class<?> c = bean.getClass();
return "com.alibaba.dubbo.config.spring.ServiceBean".equals(c.getName())
|| "org.apache.dubbo.config.spring.ServiceBean".equals(c.getName());
}
@Override
public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException {
if (!this.isRemoting(bean, beanName)) {
return null;
}
try {
RemotingDesc serviceBeanDesc = new RemotingDesc();
Class<?> interfaceClass = (Class<?>)ReflectionUtil.invokeMethod(bean, "getInterfaceClass");
String interfaceClassName = (String)ReflectionUtil.getFieldValue(bean, "interfaceName");
String version = (String)ReflectionUtil.invokeMethod(bean, "getVersion");
String group = (String)ReflectionUtil.invokeMethod(bean, "getGroup");
serviceBeanDesc.setInterfaceClass(interfaceClass);
serviceBeanDesc.setInterfaceClassName(interfaceClassName);
serviceBeanDesc.setUniqueId(version);
serviceBeanDesc.setGroup(group);
serviceBeanDesc.setProtocol(Protocols.DUBBO);
if (isService(bean, beanName)) {
Object targetBean = ReflectionUtil.getFieldValue(bean, "ref");
serviceBeanDesc.setTargetBean(targetBean);
}
return serviceBeanDesc;
} catch (Throwable t) {
throw new FrameworkException(t);
}
}
@Override
public short getProtocol() {
return Protocols.DUBBO;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting.parser;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import io.seata.common.util.ReflectionUtil;
/**
* dubbo attribute analysis
*
* @author zhangsen
*/
public class DubboUtil {
private DubboUtil() {
}
private static final String ALIBABA_DUBBO_PROXY_NAME_PREFIX = "com.alibaba.dubbo.common.bytecode.proxy";
private static final String APACHE_DUBBO_PROXY_NAME_PREFIX = "org.apache.dubbo.common.bytecode.proxy";
/**
* get the interface class of the dubbo proxy which be generated by javaassist
*
* @param proxyBean the proxy bean
* @return the assist interface
* @throws NoSuchFieldException the no such field exception
* @throws SecurityException the security exception
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws NoSuchMethodException the no such method exception
* @throws InvocationTargetException the invocation target exception
*/
public static Class<?> getAssistInterface(Object proxyBean)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException,
NoSuchMethodException, InvocationTargetException {
if (proxyBean == null) {
return null;
}
if (!isDubboProxyName(proxyBean.getClass().getName())) {
return null;
}
Field handlerField = proxyBean.getClass().getDeclaredField("handler");
handlerField.setAccessible(true);
Object invokerInvocationHandler = handlerField.get(proxyBean);
Field invokerField = invokerInvocationHandler.getClass().getDeclaredField("invoker");
invokerField.setAccessible(true);
Object invoker = invokerField.get(invokerInvocationHandler);
Field failoverClusterInvokerField = invoker.getClass().getDeclaredField("invoker");
failoverClusterInvokerField.setAccessible(true);
Object failoverClusterInvoker = failoverClusterInvokerField.get(invoker);
return (Class<?>)ReflectionUtil.invokeMethod(failoverClusterInvoker,
"getInterface");
}
public static boolean isDubboProxyName(String name) {
return name.startsWith(ALIBABA_DUBBO_PROXY_NAME_PREFIX) || name.startsWith(APACHE_DUBBO_PROXY_NAME_PREFIX);
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting.parser;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.ReflectionUtil;
import io.seata.rm.tcc.remoting.Protocols;
import io.seata.rm.tcc.remoting.RemotingDesc;
/**
* HSF Remote Bean Parser
*
* @author ppf@jiumao.org
*/
public class HSFRemotingParser extends AbstractedRemotingParser {
/**
* is HSF env
*/
private static volatile boolean isHsf;
static {
// check HSF runtime env
try {
Class.forName("com.taobao.hsf.app.api.util.HSFApiConsumerBean");
Class.forName("com.taobao.hsf.app.api.util.HSFApiProviderBean");
Class.forName("com.taobao.hsf.app.spring.util.HSFSpringConsumerBean");
Class.forName("com.taobao.hsf.app.spring.util.HSFSpringProviderBean");
isHsf = true;
} catch (ClassNotFoundException e) {
isHsf = false;
}
}
@Override
public boolean isRemoting(Object bean, String beanName) {
return isHsf && (isReference(bean, beanName) || isService(bean, beanName));
}
@Override
public boolean isReference(Object bean, String beanName) {
String beanClassName = bean.getClass().getName();
return isHsf && ("com.taobao.hsf.app.spring.util.HSFSpringConsumerBean".equals(beanClassName) || "org.springframework.beans.factory.FactoryBean".equals(beanClassName));
}
@Override
public boolean isService(Object bean, String beanName) {
String beanClassName = bean.getClass().getName();
return isHsf && "com.taobao.hsf.app.spring.util.HSFSpringProviderBean".equals(beanClassName);
}
@Override
public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException {
if (!this.isRemoting(bean, beanName)) {
return null;
}
try {
if (isReference(bean, beanName)) {
Object consumerBean = ReflectionUtil.getFieldValue(bean, "consumerBean");
Object metadata = ReflectionUtil.invokeMethod(consumerBean, "getMetadata");
Class<?> interfaceClass = (Class<?>) ReflectionUtil.invokeMethod(metadata, "getIfClazz");
String interfaceClassName = (String) ReflectionUtil.invokeMethod(metadata, "getInterfaceName");
String uniqueId = (String) ReflectionUtil.invokeMethod(metadata, "getVersion");
String group = (String) ReflectionUtil.invokeMethod(metadata, "getGroup");
RemotingDesc serviceBeanDesc = new RemotingDesc();
serviceBeanDesc.setInterfaceClass(interfaceClass);
serviceBeanDesc.setInterfaceClassName(interfaceClassName);
serviceBeanDesc.setUniqueId(uniqueId);
serviceBeanDesc.setGroup(group);
serviceBeanDesc.setProtocol(Protocols.HSF);
return serviceBeanDesc;
} else if (isService(bean, beanName)) {
Object consumerBean = ReflectionUtil.getFieldValue(bean, "providerBean");
Object metadata = ReflectionUtil.invokeMethod(consumerBean, "getMetadata");
String interfaceClassName = (String) ReflectionUtil.invokeMethod(metadata, "getInterfaceName");
Class<?> interfaceClass = (Class<?>) Class.forName(interfaceClassName);
String uniqueId = (String) ReflectionUtil.invokeMethod(metadata, "getVersion");
String group = (String) ReflectionUtil.invokeMethod(metadata, "getGroup");
RemotingDesc serviceBeanDesc = new RemotingDesc();
serviceBeanDesc.setInterfaceClass(interfaceClass);
serviceBeanDesc.setInterfaceClassName(interfaceClassName);
serviceBeanDesc.setUniqueId(uniqueId);
serviceBeanDesc.setGroup(group);
Object targetBean = ReflectionUtil.getFieldValue(metadata, "target");
serviceBeanDesc.setTargetBean(targetBean);
serviceBeanDesc.setProtocol(Protocols.HSF);
return serviceBeanDesc;
}
} catch (Throwable t) {
throw new FrameworkException(t);
}
return null;
}
@Override
public short getProtocol() {
return Protocols.HSF;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting.parser;
import java.util.Set;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.ReflectionUtil;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.remoting.Protocols;
import io.seata.rm.tcc.remoting.RemotingDesc;
/**
* local tcc bean parsing
*
* @author zhangsen
*/
public class LocalTCCRemotingParser extends AbstractedRemotingParser {
@Override
public boolean isReference(Object bean, String beanName) {
Class<?> classType = bean.getClass();
Set<Class<?>> interfaceClasses = ReflectionUtil.getInterfaces(classType);
for (Class<?> interClass : interfaceClasses) {
if (interClass.isAnnotationPresent(LocalTCC.class)) {
return true;
}
}
return false;
}
@Override
public boolean isService(Object bean, String beanName) {
Class<?> classType = bean.getClass();
Set<Class<?>> interfaceClasses = ReflectionUtil.getInterfaces(classType);
for (Class<?> interClass : interfaceClasses) {
if (interClass.isAnnotationPresent(LocalTCC.class)) {
return true;
}
}
return false;
}
@Override
public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException {
if (!this.isRemoting(bean, beanName)) {
return null;
}
RemotingDesc remotingDesc = new RemotingDesc();
remotingDesc.setReference(true);
remotingDesc.setProtocol(Protocols.IN_JVM);
Class<?> classType = bean.getClass();
Set<Class<?>> interfaceClasses = ReflectionUtil.getInterfaces(classType);
for (Class<?> interClass : interfaceClasses) {
if (interClass.isAnnotationPresent(LocalTCC.class)) {
remotingDesc.setInterfaceClassName(interClass.getName());
remotingDesc.setInterfaceClass(interClass);
remotingDesc.setTargetBean(bean);
return remotingDesc;
}
}
throw new FrameworkException("Couldn't parser any Remoting info");
}
@Override
public short getProtocol() {
return Protocols.IN_JVM;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting.parser;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.ReflectionUtil;
import io.seata.rm.tcc.remoting.Protocols;
import io.seata.rm.tcc.remoting.RemotingDesc;
/**
* sofa-rpc remoting bean parsing
*
* @author zhangsen
*/
public class SofaRpcRemotingParser extends AbstractedRemotingParser {
@Override
public boolean isReference(Object bean, String beanName)
throws FrameworkException {
String beanClassName = bean.getClass().getName();
return "com.alipay.sofa.runtime.spring.factory.ReferenceFactoryBean".equals(beanClassName);
}
@Override
public boolean isService(Object bean, String beanName) throws FrameworkException {
String beanClassName = bean.getClass().getName();
return "com.alipay.sofa.runtime.spring.factory.ServiceFactoryBean".equals(beanClassName);
}
@Override
public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException {
if (!this.isRemoting(bean, beanName)) {
return null;
}
try {
RemotingDesc serviceBeanDesc = new RemotingDesc();
Class<?> interfaceClass = (Class<?>)ReflectionUtil.invokeMethod(bean, "getInterfaceClass");
String interfaceClassName = (String)ReflectionUtil.getFieldValue(bean, "interfaceType");
String uniqueId = (String)ReflectionUtil.getFieldValue(bean, "uniqueId");
serviceBeanDesc.setInterfaceClass(interfaceClass);
serviceBeanDesc.setInterfaceClassName(interfaceClassName);
serviceBeanDesc.setUniqueId(uniqueId);
serviceBeanDesc.setProtocol(Protocols.SOFA_RPC);
if (isService(bean, beanName)) {
Object targetBean = ReflectionUtil.getFieldValue(bean, "ref");
serviceBeanDesc.setTargetBean(targetBean);
}
return serviceBeanDesc;
} catch (Throwable t) {
throw new FrameworkException(t);
}
}
@Override
public short getProtocol() {
return Protocols.SOFA_RPC;
}
}

View File

@@ -0,0 +1 @@
io.seata.rm.tcc.TCCResourceManager

View File

@@ -0,0 +1 @@
io.seata.rm.tcc.RMHandlerTCC

View File

@@ -0,0 +1,4 @@
io.seata.rm.tcc.remoting.parser.DubboRemotingParser
io.seata.rm.tcc.remoting.parser.LocalTCCRemotingParser
io.seata.rm.tcc.remoting.parser.SofaRpcRemotingParser
io.seata.rm.tcc.remoting.parser.HSFRemotingParser

View File

@@ -0,0 +1,63 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
import java.util.List;
/**
* The interface Tcc action.
*
* @author zhangsen
*/
@LocalTCC
public interface TccAction {
/**
* Prepare boolean.
*
* @param actionContext the action context
* @param a the a
* @param b the b
* @param tccParam the tcc param
* @return the boolean
*/
@TwoPhaseBusinessAction(name = "tccActionForTest" , commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext,
@BusinessActionContextParameter(paramName = "a") int a,
@BusinessActionContextParameter(paramName = "b", index = 0) List b,
@BusinessActionContextParameter(isParamInProperty = true) TccParam tccParam);
/**
* Commit boolean.
*
* @param actionContext the action context
* @return the boolean
*/
public boolean commit(BusinessActionContext actionContext);
/**
* Rollback boolean.
*
* @param actionContext the action context
* @return the boolean
*/
public boolean rollback(BusinessActionContext actionContext);
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc;
import io.seata.rm.tcc.api.BusinessActionContext;
import java.util.List;
/**
* The type Tcc action.
*
* @author zhangsen
*/
public class TccActionImpl implements TccAction {
@Override
public boolean prepare(BusinessActionContext actionContext,
int a,
List b,
TccParam TccParam ) {
return true;
}
@Override
public boolean commit(BusinessActionContext actionContext) {
return true;
}
@Override
public boolean rollback(BusinessActionContext actionContext) {
return true;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
/**
* The type Tcc param.
*
* @author zhangsen
*/
public class TccParam {
/**
* The Num.
*/
protected int num;
/**
* The Email.
*/
@BusinessActionContextParameter(paramName = "email")
protected String email;
/**
* Instantiates a new Tcc param.
*
* @param num the num
* @param email the email
*/
public TccParam(int num, String email) {
this.num = num;
this.email = email;
}
/**
* Gets num.
*
* @return the num
*/
public int getNum() {
return num;
}
/**
* Sets num.
*
* @param num the num
*/
public void setNum(int num) {
this.num = num;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.interceptor;
import io.seata.rm.tcc.TccAction;
import io.seata.rm.tcc.TccParam;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* The type Action interceptor handler test.
*
* @author zhangsen
*/
public class ActionInterceptorHandlerTest {
/**
* The Action interceptor handler.
*/
protected ActionInterceptorHandler actionInterceptorHandler = new ActionInterceptorHandler();
/**
* Test business action context.
*
* @throws NoSuchMethodException the no such method exception
*/
@Test
public void testBusinessActionContext() throws NoSuchMethodException {
Method prepareMethod = TccAction.class.getDeclaredMethod("prepare",
BusinessActionContext.class, int.class, List.class, TccParam.class);
List<Object> list = new ArrayList<>();
list.add("b");
TccParam tccParam = new TccParam (1, "abc@ali.com");
Map<String, Object> paramContext = actionInterceptorHandler.fetchActionRequestContext(prepareMethod,
new Object[]{null, 10, list, tccParam});
System.out.println(paramContext);
Assertions.assertEquals(10, paramContext.get("a"));
Assertions.assertEquals("b", paramContext.get("b"));
Assertions.assertEquals("abc@ali.com", paramContext.get("email"));
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.rm.tcc.remoting.parser;
import io.seata.rm.tcc.TccAction;
import io.seata.rm.tcc.TccActionImpl;
import io.seata.rm.tcc.remoting.RemotingDesc;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* The type Local tcc remoting parser test.
*
* @author zhangsen
*/
public class LocalTCCRemotingParserTest {
/**
* The Local tcc remoting parser.
*/
LocalTCCRemotingParser localTCCRemotingParser = new LocalTCCRemotingParser();
/**
* Test service parser.
*/
@Test
public void testServiceParser(){
TccActionImpl tccAction = new TccActionImpl();
boolean result = localTCCRemotingParser.isService(tccAction, "a");
Assertions.assertTrue(result);
}
/**
* Test reference parser.
*/
@Test
public void testReferenceParser(){
TccActionImpl tccAction = new TccActionImpl();
boolean result = localTCCRemotingParser.isReference(tccAction, "b");
Assertions.assertTrue(result);
}
/**
* Test service desc.
*/
@Test
public void testServiceDesc(){
TccActionImpl tccAction = new TccActionImpl();
RemotingDesc remotingDesc = localTCCRemotingParser.getServiceDesc(tccAction, "c");
Assertions.assertNotNull(remotingDesc);
Assertions.assertEquals(remotingDesc.getInterfaceClassName(), "io.seata.rm.tcc.TccAction");
Assertions.assertEquals(remotingDesc.getInterfaceClass(), TccAction.class);
Assertions.assertEquals(remotingDesc.getTargetBean(), tccAction);
}
}