diff --git a/all/pom.xml b/all/pom.xml index 433a06ba493..caf0b559761 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -332,6 +332,11 @@ + + io.seata + seata-saga-annotation-mode + ${project.version} + diff --git a/common/src/main/java/io/seata/common/ConfigurationKeys.java b/common/src/main/java/io/seata/common/ConfigurationKeys.java index 4e928951089..65ba81b1e07 100644 --- a/common/src/main/java/io/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/io/seata/common/ConfigurationKeys.java @@ -231,6 +231,11 @@ public interface ConfigurationKeys { */ String TCC_ACTION_INTERCEPTOR_ORDER = CLIENT_RM_PREFIX + "tccActionInterceptorOrder"; + /** + * The constant SAGA_ACTION_INTERCEPTOR_ORDER. + */ + String SAGA_ACTION_INTERCEPTOR_ORDER = CLIENT_RM_PREFIX + "sagaActionInterceptorOrder"; + /** * The constant CLIENT_TM_PREFIX. */ diff --git a/common/src/main/java/io/seata/common/Constants.java b/common/src/main/java/io/seata/common/Constants.java index aa4a0241740..4aeb0a42500 100644 --- a/common/src/main/java/io/seata/common/Constants.java +++ b/common/src/main/java/io/seata/common/Constants.java @@ -69,7 +69,7 @@ public interface Constants { /** * Use TCC fence */ - String USE_TCC_FENCE = "useTCCFence"; + String USE_COMMON_FENCE = "useTCCFence"; /** * phase one method name @@ -94,7 +94,7 @@ public interface Constants { /** * branch context */ - String TCC_ACTION_CONTEXT = "actionContext"; + String TX_ACTION_CONTEXT = "actionContext"; /** * default charset name @@ -160,5 +160,10 @@ public interface Constants { * The constant SKIP_CHECK_LOCK */ String SKIP_CHECK_LOCK = "skipCheckLock"; + + /** + * phase two compensation method name + */ + String COMPENSATION_METHOD = "sys::compensation"; } diff --git a/common/src/main/java/io/seata/common/DefaultValues.java b/common/src/main/java/io/seata/common/DefaultValues.java index bebf182566a..d0d1b4f41db 100644 --- a/common/src/main/java/io/seata/common/DefaultValues.java +++ b/common/src/main/java/io/seata/common/DefaultValues.java @@ -147,23 +147,28 @@ public interface DefaultValues { */ int TCC_ACTION_INTERCEPTOR_ORDER = Integer.MIN_VALUE + 1000; + /** + * the constant SAGA_ACTION_INTERCEPTOR_ORDER + */ + int SAGA_ACTION_INTERCEPTOR_ORDER = Integer.MIN_VALUE + 1000; + /** * the constant DEFAULT_DISTRIBUTED_LOCK_EXPIRE */ int DEFAULT_DISTRIBUTED_LOCK_EXPIRE = 10000; /** - * the constant DEFAULT_TCC_FENCE_CLEAN_PERIOD + * the constant DEFAULT_COMMON_FENCE_CLEAN_PERIOD */ - int DEFAULT_TCC_FENCE_CLEAN_PERIOD = 1; + int DEFAULT_COMMON_FENCE_CLEAN_PERIOD = 1; /** - * the constant DEFAULT_TCC_FENCE_LOG_TABLE_NAME + * the constant DEFAULT_COMMON_FENCE_LOG_TABLE_NAME */ - String DEFAULT_TCC_FENCE_LOG_TABLE_NAME = "tcc_fence_log"; + String DEFAULT_COMMON_FENCE_LOG_TABLE_NAME = "tcc_fence_log"; /** - * the constant TCC_FENCE_BEAN_NAME + * the constant COMMON_FENCE_BEAN_NAME */ - String TCC_FENCE_BEAN_NAME = "tccFenceConfig"; + String COMMON_FENCE_BEAN_NAME = "tccFenceConfig"; /** * the constant DEFAULT_RPC_RM_REQUEST_TIMEOUT diff --git a/core/src/main/java/io/seata/core/model/BranchType.java b/core/src/main/java/io/seata/core/model/BranchType.java index f7ecfa09848..459655ffe57 100644 --- a/core/src/main/java/io/seata/core/model/BranchType.java +++ b/core/src/main/java/io/seata/core/model/BranchType.java @@ -37,6 +37,11 @@ public enum BranchType { * The SAGA. */ SAGA, + + /** + * The SAGA annotation mode + */ + SAGA_ANNOTATION, /** * The XA. diff --git a/saga/pom.xml b/saga/pom.xml index 9f781e5f84f..792b99a1c7a 100644 --- a/saga/pom.xml +++ b/saga/pom.xml @@ -36,6 +36,7 @@ seata-saga-rm seata-saga-tm seata-saga-engine-store + seata-saga-annotation-mode diff --git a/saga/seata-saga-annotation-mode/pom.xml b/saga/seata-saga-annotation-mode/pom.xml new file mode 100644 index 00000000000..4f1176c57ed --- /dev/null +++ b/saga/seata-saga-annotation-mode/pom.xml @@ -0,0 +1,27 @@ + + + + seata-saga + io.seata + ${revision} + + 4.0.0 + seata-saga-annotation-mode ${project.version} + seata-saga-annotation-mode + + + + ${project.groupId} + seata-spring + ${project.version} + + + io.seata + seata-saga-rm + ${project.version} + + + + \ No newline at end of file diff --git a/saga/seata-saga-annotation-mode/src/main/java/io/seata/saga/annotation/api/SagaTransactional.java b/saga/seata-saga-annotation-mode/src/main/java/io/seata/saga/annotation/api/SagaTransactional.java new file mode 100644 index 00000000000..4566a3c952f --- /dev/null +++ b/saga/seata-saga-annotation-mode/src/main/java/io/seata/saga/annotation/api/SagaTransactional.java @@ -0,0 +1,81 @@ +/* + * 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.saga.annotation.api; + +import io.seata.rm.tcc.api.BusinessActionContext; +import io.seata.rm.tcc.api.BusinessActionContextParameter; +import io.seata.rm.tcc.api.BusinessActionContextUtil; +import io.seata.saga.annotation.interceptor.SagaActionInterceptor; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; + +/** + * SAGA annotation. + * Define a SAGA interface, which added on the saga commit method. + * Must be used with `@LocalService`. + * + * @author ruishansun + * @see io.seata.spring.annotation.LocalService // local transaction annotation, which added on the transaction interface. It can't be left out. + * @see io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary(Object, String, Object) // the scanner for TM, GlobalLock, TCC mode, SAGA mode + * @see SagaActionInterceptor // the interceptor of SAGA mode + * @see BusinessActionContext + * @see BusinessActionContextUtil + * @see BusinessActionContextParameter + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Inherited +public @interface SagaTransactional { + + /** + * Saga bean name, must be unique + * + * @return the string + */ + String name(); + + /** + * compensation method name + * + * @return the string + */ + String compensationMethod() default "compensation"; + + /** + * delay branch report while sharing params to saga phase 2 to enhance performance + * + * @return isDelayReport + */ + boolean isDelayReport() default false; + + /** + * whether use common fence (idempotent,non_rollback,suspend) + * + * @return the boolean + */ + boolean useCommonFence() default false; + + /** + * compensation method's args + * + * @return the Class[] + */ + Class[] compensationArgsClasses() default {BusinessActionContext.class}; +} diff --git a/saga/seata-saga-annotation-mode/src/main/java/io/seata/saga/annotation/autoproxy/SagaTransactionAutoProxy.java b/saga/seata-saga-annotation-mode/src/main/java/io/seata/saga/annotation/autoproxy/SagaTransactionAutoProxy.java new file mode 100644 index 00000000000..e70e96a5edd --- /dev/null +++ b/saga/seata-saga-annotation-mode/src/main/java/io/seata/saga/annotation/autoproxy/SagaTransactionAutoProxy.java @@ -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.saga.annotation.autoproxy; + +import io.seata.common.exception.FrameworkException; +import io.seata.rm.DefaultResourceManager; +import io.seata.saga.annotation.interceptor.SagaActionInterceptor; +import io.seata.saga.annotation.api.SagaTransactional; +import io.seata.saga.rm.annotation.SagaAnnotationResource; +import io.seata.spring.autoproxy.IsTransactionProxyResult; +import io.seata.spring.autoproxy.TransactionAutoProxy; +import io.seata.spring.remoting.Protocols; +import io.seata.spring.remoting.RemotingDesc; +import io.seata.spring.remoting.parser.DefaultRemotingParser; + +import java.lang.reflect.Method; + +/** + * the saga implements of TransactionAutoProxy + * + * @author ruishansun + */ +public class SagaTransactionAutoProxy implements TransactionAutoProxy { + + @Override + public IsTransactionProxyResult isTransactionProxyTargetBean(RemotingDesc remotingDesc) { + + if (remotingDesc == null) { + return new IsTransactionProxyResult(); + } + //check if it is saga bean + Class sagaInterfaceClazz = remotingDesc.getInterfaceClass(); + Method[] methods = sagaInterfaceClazz.getMethods(); + for (Method method : methods) { + SagaTransactional sagaTransactional = method.getAnnotation(SagaTransactional.class); + if (sagaTransactional != null && (Protocols.IN_JVM == remotingDesc.getProtocol() || remotingDesc.isReference())) { + this.registryResource(remotingDesc); + IsTransactionProxyResult result = new IsTransactionProxyResult(); + result.setProxyTargetBean(true); + result.setUseCommonFence(sagaTransactional.useCommonFence()); + result.setMethodInterceptor(new SagaActionInterceptor(remotingDesc)); + return result; + } + } + return new IsTransactionProxyResult(); + } + + /** + * register saga resource + * + * @param remotingDesc the remotingDesc + */ + private void registryResource(RemotingDesc remotingDesc) { + if (remotingDesc.isService()) { + try { + Class interfaceClass = remotingDesc.getInterfaceClass(); + Method[] methods = interfaceClass.getMethods(); + //service bean, registry resource + Object targetBean = remotingDesc.getTargetBean(); + for (Method m : methods) { + SagaTransactional sagaTransactional = m.getAnnotation(SagaTransactional.class); + if (sagaTransactional != null) { + SagaAnnotationResource sagaAnnotationResource = new SagaAnnotationResource(); + sagaAnnotationResource.setActionName(sagaTransactional.name()); + sagaAnnotationResource.setTargetBean(targetBean); + sagaAnnotationResource.setCommitMethod(m); + sagaAnnotationResource.setCompensationMethodName(sagaTransactional.compensationMethod()); + sagaAnnotationResource.setCompensationMethod(interfaceClass.getMethod(sagaTransactional.compensationMethod(), + sagaTransactional.compensationArgsClasses())); + // set argsClasses + sagaAnnotationResource.setCompensationArgsClasses(sagaTransactional.compensationArgsClasses()); + // set phase two method's keys + sagaAnnotationResource.setPhaseTwoCompensationKeys(DefaultRemotingParser.get().getTwoPhaseArgs(sagaAnnotationResource.getCompensationMethod(), + sagaTransactional.compensationArgsClasses())); + //registry saga annotation resource + sagaAnnotationResource.setResourceGroupId(sagaTransactional.name()); + DefaultResourceManager.get().registerResource(sagaAnnotationResource); + } + } + } catch (Throwable t) { + throw new FrameworkException(t, "parser remoting service error"); + } + } + } +} diff --git a/saga/seata-saga-annotation-mode/src/main/java/io/seata/saga/annotation/interceptor/SagaActionInterceptor.java b/saga/seata-saga-annotation-mode/src/main/java/io/seata/saga/annotation/interceptor/SagaActionInterceptor.java new file mode 100644 index 00000000000..3db03d7f138 --- /dev/null +++ b/saga/seata-saga-annotation-mode/src/main/java/io/seata/saga/annotation/interceptor/SagaActionInterceptor.java @@ -0,0 +1,147 @@ +/* + * 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.saga.annotation.interceptor; + +import io.seata.common.Constants; +import io.seata.common.DefaultValues; +import io.seata.config.ConfigurationChangeEvent; +import io.seata.config.ConfigurationChangeListener; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.saga.annotation.api.SagaTransactional; +import io.seata.spring.interceptor.ActionInterceptorHandler; +import io.seata.spring.interceptor.TwoPhaseBusinessActionParam; +import io.seata.spring.remoting.RemotingDesc; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.core.Ordered; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import static io.seata.common.ConfigurationKeys.SAGA_ACTION_INTERCEPTOR_ORDER; +import static io.seata.common.DefaultValues.DEFAULT_DISABLE_GLOBAL_TRANSACTION; + +/** + * SAGA Interceptor + * + * @author ruishansun + */ +public class SagaActionInterceptor implements MethodInterceptor, ConfigurationChangeListener, Ordered { + + private static final Logger LOGGER = LoggerFactory.getLogger(SagaActionInterceptor.class); + + private static final int ORDER_NUM = ConfigurationFactory.getInstance().getInt(SAGA_ACTION_INTERCEPTOR_ORDER, + DefaultValues.SAGA_ACTION_INTERCEPTOR_ORDER); + + /** + * TODO Singleton? + */ + private ActionInterceptorHandler actionInterceptorHandler = new ActionInterceptorHandler(); + + private volatile boolean disable = ConfigurationFactory.getInstance().getBoolean( + ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, DEFAULT_DISABLE_GLOBAL_TRANSACTION); + + /** + * remoting bean info + */ + protected RemotingDesc remotingDesc; + + /** + * Instantiates a new Saga action interceptor. + */ + public SagaActionInterceptor() { + } + + /** + * Instantiates a new Saga action interceptor. + * + * @param remotingDesc the remoting desc + */ + public SagaActionInterceptor(RemotingDesc remotingDesc) { + this.remotingDesc = remotingDesc; + } + + @Nullable + @Override + public Object invoke(@NotNull MethodInvocation invocation) throws Throwable { + if (!RootContext.inGlobalTransaction() || disable || RootContext.inTccBranch()) { + //not in transaction, or this interceptor is disabled + return invocation.proceed(); + } + Method method = actionInterceptorHandler.getActionInterfaceMethod(invocation, this.remotingDesc); + SagaTransactional sagaTransactional = method.getAnnotation(SagaTransactional.class); + //commit method + if (sagaTransactional != null) { + //save the xid + String xid = RootContext.getXID(); + //save the previous branchType + BranchType previousBranchType = RootContext.getBranchType(); + //if not SAGA, bind SAGA branchType + if (BranchType.SAGA_ANNOTATION != previousBranchType) { + RootContext.bindBranchType(BranchType.SAGA_ANNOTATION); + } + try { + TwoPhaseBusinessActionParam businessActionParam = new TwoPhaseBusinessActionParam(); + businessActionParam.setActionName(sagaTransactional.name()); + businessActionParam.setDelayReport(sagaTransactional.isDelayReport()); + businessActionParam.setUseCommonFence(sagaTransactional.useCommonFence()); + businessActionParam.setBranchType(BranchType.SAGA_ANNOTATION); + Map businessActionContextMap = new HashMap<>(4); + //the phase two method name + businessActionContextMap.put(Constants.COMPENSATION_METHOD, sagaTransactional.compensationMethod()); + businessActionContextMap.put(Constants.ACTION_NAME, sagaTransactional.name()); + businessActionContextMap.put(Constants.USE_COMMON_FENCE, sagaTransactional.useCommonFence()); + businessActionParam.setBusinessActionContext(businessActionContextMap); + //Handler the Saga Aspect, and return the business result + return actionInterceptorHandler.proceed(method, invocation.getArguments(), xid, businessActionParam, + invocation::proceed); + } finally { + //if not SAGA, unbind branchType + if (BranchType.SAGA_ANNOTATION != previousBranchType) { + RootContext.unbindBranchType(); + } + //MDC remove branchId + MDC.remove(RootContext.MDC_KEY_BRANCH_ID); + } + } + + //not SAGA commit method + return invocation.proceed(); + } + + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + if (ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION.equals(event.getDataId())) { + LOGGER.info("{} config changed, old value:{}, new value:{}", ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, + disable, event.getNewValue()); + disable = Boolean.parseBoolean(event.getNewValue().trim()); + } + } + + @Override + public int getOrder() { + return ORDER_NUM; + } +} diff --git a/saga/seata-saga-annotation-mode/src/test/java/io/seata/rm/saga/SagaAction.java b/saga/seata-saga-annotation-mode/src/test/java/io/seata/rm/saga/SagaAction.java new file mode 100644 index 00000000000..8fc5b40da48 --- /dev/null +++ b/saga/seata-saga-annotation-mode/src/test/java/io/seata/rm/saga/SagaAction.java @@ -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.saga; + +import io.seata.rm.tcc.api.BusinessActionContext; +import io.seata.rm.tcc.api.BusinessActionContextParameter; +import io.seata.saga.annotation.api.SagaTransactional; +import io.seata.spring.annotation.LocalService; + +/** + * The interface Saga action. + * + * @author ruishansun + */ +@LocalService +public interface SagaAction { + + /** + * Commit boolean. + * + * @param actionContext the action context + * @param a the a + * @return the boolean + */ + @SagaTransactional(name = "sagaActionForTest", compensationMethod = "compensation") + boolean commit(BusinessActionContext actionContext, + @BusinessActionContextParameter("a") int a); + + /** + * Compensation boolean. + * + * @param actionContext the action context + * @return the boolean + */ + boolean compensation(BusinessActionContext actionContext); +} diff --git a/saga/seata-saga-annotation-mode/src/test/java/io/seata/rm/saga/SagaActionImpl.java b/saga/seata-saga-annotation-mode/src/test/java/io/seata/rm/saga/SagaActionImpl.java new file mode 100644 index 00000000000..3f49382e0df --- /dev/null +++ b/saga/seata-saga-annotation-mode/src/test/java/io/seata/rm/saga/SagaActionImpl.java @@ -0,0 +1,36 @@ +/* + * 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.saga; + +import io.seata.rm.tcc.api.BusinessActionContext; + +/** + * The type Saga action impl. + * + * @author ruishansun + */ +public class SagaActionImpl implements SagaAction { + + @Override + public boolean commit(BusinessActionContext actionContext, int a) { + return true; + } + + @Override + public boolean compensation(BusinessActionContext actionContext) { + return true; + } +} diff --git a/saga/seata-saga-annotation-mode/src/test/java/io/seata/rm/saga/remoting/parser/LocalServiceRemotingParserTest.java b/saga/seata-saga-annotation-mode/src/test/java/io/seata/rm/saga/remoting/parser/LocalServiceRemotingParserTest.java new file mode 100644 index 00000000000..80f99affed0 --- /dev/null +++ b/saga/seata-saga-annotation-mode/src/test/java/io/seata/rm/saga/remoting/parser/LocalServiceRemotingParserTest.java @@ -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.saga.remoting.parser; + +import io.seata.rm.saga.SagaAction; +import io.seata.rm.saga.SagaActionImpl; +import io.seata.spring.remoting.RemotingDesc; +import io.seata.spring.remoting.parser.LocalServiceRemotingParser; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Local service remoting parser test. + * + * @author ruishansun + */ +public class LocalServiceRemotingParserTest { + + /** + * The Local service remoting parser. + */ + LocalServiceRemotingParser localServiceRemotingParser = new LocalServiceRemotingParser(); + + /** + * Test service parser. + */ + @Test + public void testServiceParser() { + SagaActionImpl sagaActionImpl = new SagaActionImpl(); + + boolean result = localServiceRemotingParser.isService(sagaActionImpl, "a"); + Assertions.assertTrue(result); + } + + /** + * Test reference parser. + */ + @Test + public void testReferenceParser() { + SagaActionImpl sagaActionImpl = new SagaActionImpl(); + + boolean result = localServiceRemotingParser.isReference(sagaActionImpl, "b"); + Assertions.assertTrue(result); + } + + /** + * Test service desc. + */ + @Test + public void testServiceDesc() { + SagaActionImpl sagaActionImpl = new SagaActionImpl(); + + RemotingDesc remotingDesc = localServiceRemotingParser.getServiceDesc(sagaActionImpl, "c"); + Assertions.assertNotNull(remotingDesc); + + Assertions.assertEquals("io.seata.rm.saga.SagaAction", remotingDesc.getInterfaceClassName()); + Assertions.assertEquals(remotingDesc.getInterfaceClass(), SagaAction.class); + Assertions.assertEquals(remotingDesc.getTargetBean(), sagaActionImpl); + } + +} diff --git a/saga/seata-saga-rm/pom.xml b/saga/seata-saga-rm/pom.xml index 0956f235865..1e324de4322 100644 --- a/saga/seata-saga-rm/pom.xml +++ b/saga/seata-saga-rm/pom.xml @@ -44,6 +44,11 @@ seata-saga-engine ${project.version} + + io.seata + seata-spring + ${project.version} + diff --git a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResource.java b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResource.java index 8a087c3b8c4..eab48cad704 100644 --- a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResource.java +++ b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResource.java @@ -85,4 +85,5 @@ public String getApplicationId() { public void setApplicationId(String applicationId) { this.applicationId = applicationId; } + } \ No newline at end of file diff --git a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResourceManager.java b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResourceManager.java index b58120b8d6c..41e29f5f4c5 100644 --- a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResourceManager.java +++ b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResourceManager.java @@ -15,9 +15,6 @@ */ package io.seata.saga.rm; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import io.seata.common.exception.FrameworkErrorCode; import io.seata.core.exception.TransactionException; import io.seata.core.model.BranchStatus; @@ -33,8 +30,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** - * Saga resource manager + * Saga state machine resource manager * * @author lorne.cl */ @@ -60,7 +60,7 @@ public SagaResourceManager() { */ @Override public void registerResource(Resource resource) { - SagaResource sagaResource = (SagaResource)resource; + SagaResource sagaResource = (SagaResource) resource; sagaResourceCache.put(sagaResource.getResourceId(), sagaResource); super.registerResource(sagaResource); } @@ -84,19 +84,21 @@ public Map getManagedResources() { @Override public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, String applicationData) throws TransactionException { + + // Saga state machine mode try { StateMachineInstance machineInstance = StateMachineEngineHolder.getStateMachineEngine().forward(xid, null); if (ExecutionStatus.SU.equals(machineInstance.getStatus()) - && machineInstance.getCompensationStatus() == null) { + && machineInstance.getCompensationStatus() == null) { return BranchStatus.PhaseTwo_Committed; } else if (ExecutionStatus.SU.equals(machineInstance.getCompensationStatus())) { return BranchStatus.PhaseTwo_Rollbacked; } else if (ExecutionStatus.FA.equals(machineInstance.getCompensationStatus()) || ExecutionStatus.UN.equals( - machineInstance.getCompensationStatus())) { + machineInstance.getCompensationStatus())) { return BranchStatus.PhaseTwo_RollbackFailed_Retryable; } else if (ExecutionStatus.FA.equals(machineInstance.getStatus()) - && machineInstance.getCompensationStatus() == null) { + && machineInstance.getCompensationStatus() == null) { return BranchStatus.PhaseOne_Failed; } @@ -127,20 +129,22 @@ public BranchStatus branchCommit(BranchType branchType, String xid, long branchI @Override public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId, String applicationData) throws TransactionException { + + // Saga state machine mode try { StateMachineInstance stateMachineInstance = StateMachineEngineHolder.getStateMachineEngine().reloadStateMachineInstance(xid); if (stateMachineInstance == null) { return BranchStatus.PhaseTwo_Rollbacked; } if (RecoverStrategy.Forward.equals(stateMachineInstance.getStateMachine().getRecoverStrategy()) - && (GlobalStatus.TimeoutRollbacking.name().equals(applicationData) - || GlobalStatus.TimeoutRollbackRetrying.name().equals(applicationData))) { + && (GlobalStatus.TimeoutRollbacking.name().equals(applicationData) + || GlobalStatus.TimeoutRollbackRetrying.name().equals(applicationData))) { LOGGER.warn("Retry by custom recover strategy [Forward] on timeout, SAGA global[{}]", xid); return BranchStatus.PhaseTwo_CommitFailed_Retryable; } stateMachineInstance = StateMachineEngineHolder.getStateMachineEngine().compensate(xid, - null); + null); if (ExecutionStatus.SU.equals(stateMachineInstance.getCompensationStatus())) { return BranchStatus.PhaseTwo_Rollbacked; } diff --git a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/annotation/RMHandlerSagaAnnotation.java b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/annotation/RMHandlerSagaAnnotation.java new file mode 100644 index 00000000000..d4af54b561b --- /dev/null +++ b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/annotation/RMHandlerSagaAnnotation.java @@ -0,0 +1,39 @@ +/* + * 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.saga.rm.annotation; + +import io.seata.core.model.BranchType; +import io.seata.core.model.ResourceManager; +import io.seata.rm.AbstractRMHandler; +import io.seata.rm.DefaultResourceManager; + +/** + * The type Rm handler SAGA annotation. + * + * @author ruishansun + */ +public class RMHandlerSagaAnnotation extends AbstractRMHandler { + + @Override + protected ResourceManager getResourceManager() { + return DefaultResourceManager.get().getResourceManager(BranchType.SAGA_ANNOTATION); + } + + @Override + public BranchType getBranchType() { + return BranchType.SAGA_ANNOTATION; + } +} diff --git a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/annotation/SagaAnnotationResource.java b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/annotation/SagaAnnotationResource.java new file mode 100644 index 00000000000..0e3dcd2fe40 --- /dev/null +++ b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/annotation/SagaAnnotationResource.java @@ -0,0 +1,131 @@ +/* + * 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.saga.rm.annotation; + +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; + +import java.lang.reflect.Method; + +/** + * Saga annotation mode resource (Only register application as a saga annotation mode resource) + * + * @author ruishansun + */ +public class SagaAnnotationResource implements Resource { + + private String resourceGroupId = "DEFAULT"; + + private Object targetBean; + + private String actionName; + + private Method commitMethod; + + private Method compensationMethod; + + private String compensationMethodName; + + private Class[] compensationArgsClasses; + + private String[] phaseTwoCompensationKeys; + + public Object getTargetBean() { + return targetBean; + } + + public void setTargetBean(Object targetBean) { + this.targetBean = targetBean; + } + + public String getActionName() { + return actionName; + } + + public void setActionName(String actionName) { + this.actionName = actionName; + } + + public Method getCommitMethod() { + return commitMethod; + } + + public void setCommitMethod(Method commitMethod) { + this.commitMethod = commitMethod; + } + + public Method getCompensationMethod() { + return compensationMethod; + } + + public void setCompensationMethod(Method compensationMethod) { + this.compensationMethod = compensationMethod; + } + + public String getCompensationMethodName() { + return compensationMethodName; + } + + public void setCompensationMethodName(String compensationMethodName) { + this.compensationMethodName = compensationMethodName; + } + + public Class[] getCompensationArgsClasses() { + return compensationArgsClasses; + } + + public void setCompensationArgsClasses(Class[] compensationArgsClasses) { + this.compensationArgsClasses = compensationArgsClasses; + } + + public String[] getPhaseTwoCompensationKeys() { + return phaseTwoCompensationKeys; + } + + public void setPhaseTwoCompensationKeys(String[] phaseTwoCompensationKeys) { + this.phaseTwoCompensationKeys = phaseTwoCompensationKeys; + } + + @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; + } + + /** + * Gets get branch type. + * + * @return the get branch type + */ + @Override + public BranchType getBranchType() { + return BranchType.SAGA_ANNOTATION; + } +} diff --git a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/annotation/SagaAnnotationResourceManager.java b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/annotation/SagaAnnotationResourceManager.java new file mode 100644 index 00000000000..0513cb8b2fc --- /dev/null +++ b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/annotation/SagaAnnotationResourceManager.java @@ -0,0 +1,146 @@ +/* + * 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.saga.rm.annotation; + +import io.seata.common.Constants; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.exception.SkipCallbackWrapperException; +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; +import io.seata.rm.tcc.api.BusinessActionContextUtil; +import io.seata.spring.fence.CommonFenceHandler; +import io.seata.spring.remoting.TwoPhaseResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Saga annotation resource manager + * + * @author ruishansun + */ +public class SagaAnnotationResourceManager extends AbstractResourceManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(SagaAnnotationResourceManager.class); + + /** + * Saga annotation resource cache + */ + private final Map sagaAnnotationResourceCache = new ConcurrentHashMap<>(); + + /** + * Instantiates a new saga annotation resource manager. + */ + public SagaAnnotationResourceManager() { + } + + /** + * registry saga annotation resource + * + * @param resource The resource to be managed. + */ + @Override + public void registerResource(Resource resource) { + SagaAnnotationResource sagaAnnotationResource = (SagaAnnotationResource) resource; + sagaAnnotationResourceCache.put(sagaAnnotationResource.getResourceId(), sagaAnnotationResource); + super.registerResource(sagaAnnotationResource); + } + + @Override + public Map getManagedResources() { + return sagaAnnotationResourceCache; + } + + @Override + public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, String applicationData) { + + // Saga annotation mode + return BranchStatus.PhaseTwo_Committed; + } + + @Override + public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId, String applicationData) { + // Saga annotation mode + SagaAnnotationResource sagaAnnotationResource = (SagaAnnotationResource) sagaAnnotationResourceCache.get(resourceId); + if (sagaAnnotationResource == null) { + throw new ShouldNeverHappenException(String.format("Saga annotation resource is not exist, resourceId: %s", resourceId)); + } + Object targetSagaBean = sagaAnnotationResource.getTargetBean(); + Method compensationMethod = sagaAnnotationResource.getCompensationMethod(); + if (targetSagaBean == null || compensationMethod == null) { + throw new ShouldNeverHappenException(String.format("Saga target Bean or compensationMethod is not available, resourceId: %s", resourceId)); + } + try { + //BusinessActionContext + BusinessActionContext businessActionContext = BusinessActionContextUtil.getBusinessActionContext(xid, branchId, resourceId, applicationData); + Object[] args = this.getTwoPhaseCompensationArgs(sagaAnnotationResource, businessActionContext); + + Object ret; + boolean result; + // add idempotent and anti hanging + if (Boolean.TRUE.equals(businessActionContext.getActionContext(Constants.USE_COMMON_FENCE))) { + try { + result = CommonFenceHandler.rollbackFence(compensationMethod, targetSagaBean, xid, branchId, args, sagaAnnotationResource.getActionName()); + } catch (SkipCallbackWrapperException | UndeclaredThrowableException e) { + throw e.getCause(); + } + } else { + ret = compensationMethod.invoke(targetSagaBean, args); + if (ret != null) { + if (ret instanceof TwoPhaseResult) { + result = ((TwoPhaseResult) ret).isSuccess(); + } else { + result = (boolean) ret; + } + } else { + result = true; + } + } + LOGGER.info("Saga resource compensation result : {}, xid: {}, branchId: {}, resourceId: {}", result, xid, branchId, resourceId); + return result ? BranchStatus.PhaseTwo_Rollbacked : BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } catch (Throwable t) { + String msg = String.format("compensation Saga resource error, resourceId: %s, xid: %s.", resourceId, xid); + LOGGER.error(msg, t); + return BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } + } + + + /** + * get phase two compensate method's args + * + * @param sagaAnnotationResource sagaAnnotationResource + * @param businessActionContext businessActionContext + * @return args + */ + private Object[] getTwoPhaseCompensationArgs(SagaAnnotationResource sagaAnnotationResource, BusinessActionContext businessActionContext) { + String[] keys = sagaAnnotationResource.getPhaseTwoCompensationKeys(); + Class[] argsCommitClasses = sagaAnnotationResource.getCompensationArgsClasses(); + return BusinessActionContextUtil.getTwoPhaseMethodParams(keys, argsCommitClasses, businessActionContext); + } + + @Override + public BranchType getBranchType() { + return BranchType.SAGA_ANNOTATION; + } +} diff --git a/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager b/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager index 69bc38af6f9..4e157dfa481 100644 --- a/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager +++ b/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager @@ -1 +1,2 @@ io.seata.saga.rm.SagaResourceManager +io.seata.saga.rm.annotation.SagaAnnotationResourceManager \ No newline at end of file diff --git a/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler b/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler index cd5b9a18bbb..b10cb62b3de 100644 --- a/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler +++ b/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler @@ -1 +1,2 @@ -io.seata.saga.rm.RMHandlerSaga \ No newline at end of file +io.seata.saga.rm.RMHandlerSaga +io.seata.saga.rm.annotation.RMHandlerSagaAnnotation \ No newline at end of file diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/SeataClientEnvironmentPostProcessor.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/SeataClientEnvironmentPostProcessor.java index 5939248427a..48145b3ca73 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/SeataClientEnvironmentPostProcessor.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/SeataClientEnvironmentPostProcessor.java @@ -16,7 +16,7 @@ package io.seata.spring.boot.autoconfigure; import io.seata.common.holder.ObjectHolder; -import io.seata.rm.tcc.config.TCCFenceConfig; +import io.seata.spring.fence.config.CommonFenceConfig; import io.seata.saga.engine.StateMachineConfig; import io.seata.spring.boot.autoconfigure.properties.SagaAsyncThreadPoolProperties; import io.seata.spring.boot.autoconfigure.properties.SeataProperties; @@ -64,7 +64,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp PROPERTY_BEAN_MAP.put(UNDO_PREFIX, UndoProperties.class); PROPERTY_BEAN_MAP.put(COMPRESS_PREFIX, UndoCompressProperties.class); PROPERTY_BEAN_MAP.put(LOAD_BALANCE_PREFIX, LoadBalanceProperties.class); - PROPERTY_BEAN_MAP.put(TCC_FENCE_PREFIX, TCCFenceConfig.class); + PROPERTY_BEAN_MAP.put(TCC_FENCE_PREFIX, CommonFenceConfig.class); PROPERTY_BEAN_MAP.put(SAGA_STATE_MACHINE_PREFIX, StateMachineConfig.class); PROPERTY_BEAN_MAP.put(SAGA_ASYNC_THREAD_POOL_PREFIX, SagaAsyncThreadPoolProperties.class); } diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/SeataTCCFenceAutoConfiguration.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/SeataTCCFenceAutoConfiguration.java index 3f4cd97a87f..a772ae66c4b 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/SeataTCCFenceAutoConfiguration.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/SeataTCCFenceAutoConfiguration.java @@ -15,7 +15,7 @@ */ package io.seata.spring.boot.autoconfigure; -import io.seata.rm.tcc.config.TCCFenceConfig; +import io.seata.spring.fence.config.CommonFenceConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -37,7 +37,7 @@ */ @ConditionalOnExpression("${seata.enabled:true}") @ConditionalOnBean(type = {"javax.sql.DataSource", "org.springframework.transaction.PlatformTransactionManager"}) -@ConditionalOnMissingBean(TCCFenceConfig.class) +@ConditionalOnMissingBean(CommonFenceConfig.class) @AutoConfigureAfter({SeataCoreAutoConfiguration.class, DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class}) public class SeataTCCFenceAutoConfiguration { @@ -46,12 +46,12 @@ public class SeataTCCFenceAutoConfiguration { @Bean @ConfigurationProperties(StarterConstants.TCC_FENCE_PREFIX) - public TCCFenceConfig tccFenceConfig( + public CommonFenceConfig tccFenceConfig( DataSource dataSource, PlatformTransactionManager transactionManager, @Qualifier(TCC_FENCE_DATA_SOURCE_BEAN_NAME) @Autowired(required = false) DataSource tccFenceDataSource, @Qualifier(TCC_FENCE_TRANSACTION_MANAGER_BEAN_NAME) @Autowired(required = false) PlatformTransactionManager tccFenceTransactionManager) { - return new TCCFenceConfig(tccFenceDataSource != null ? tccFenceDataSource : dataSource, + return new CommonFenceConfig(tccFenceDataSource != null ? tccFenceDataSource : dataSource, tccFenceTransactionManager != null ? tccFenceTransactionManager : transactionManager); } diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/properties/client/RmProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/properties/client/RmProperties.java index 705e05ff1bd..b01c860eb52 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/properties/client/RmProperties.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/java/io/seata/spring/boot/autoconfigure/properties/client/RmProperties.java @@ -29,6 +29,7 @@ import static io.seata.common.DefaultValues.DEFAULT_SAGA_JSON_PARSER; import static io.seata.common.DefaultValues.DEFAULT_TABLE_META_CHECKER_INTERVAL; import static io.seata.common.DefaultValues.TCC_ACTION_INTERCEPTOR_ORDER; +import static io.seata.common.DefaultValues.SAGA_ACTION_INTERCEPTOR_ORDER; import static io.seata.common.DefaultValues.DEFAULT_XA_BRANCH_EXECUTION_TIMEOUT; import static io.seata.common.DefaultValues.DEFAULT_XA_CONNECTION_TWO_PHASE_HOLD_TIMEOUT; import static io.seata.spring.boot.autoconfigure.StarterConstants.CLIENT_RM_PREFIX; @@ -49,6 +50,7 @@ public class RmProperties { private boolean sagaRetryPersistModeUpdate = DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE; private boolean sagaCompensatePersistModeUpdate = DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE; private int tccActionInterceptorOrder = TCC_ACTION_INTERCEPTOR_ORDER; + private int sagaActionInterceptorOrder = SAGA_ACTION_INTERCEPTOR_ORDER; private int branchExecutionTimeoutXA = DEFAULT_XA_BRANCH_EXECUTION_TIMEOUT; private int connectionTwoPhaseHoldTimeoutXA = DEFAULT_XA_CONNECTION_TWO_PHASE_HOLD_TIMEOUT; private String sqlParserType = SqlParserType.SQL_PARSER_TYPE_DRUID; @@ -164,4 +166,11 @@ public void setConnectionTwoPhaseHoldTimeoutXA(int connectionTwoPhaseHoldTimeout this.connectionTwoPhaseHoldTimeoutXA = connectionTwoPhaseHoldTimeoutXA; } + public int getSagaActionInterceptorOrder() { + return sagaActionInterceptorOrder; + } + + public void setSagaActionInterceptorOrder(int sagaActionInterceptorOrder) { + this.sagaActionInterceptorOrder = sagaActionInterceptorOrder; + } } diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 5ca2a8de55c..0e16befe433 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-client/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -248,14 +248,14 @@ "name": "seata.tcc.fence.log-table-name", "type": "java.lang.String", "description": "TCC fence log table name.", - "sourceType": "io.seata.rm.tcc.config.TCCFenceConfig", + "sourceType": "io.seata.spring.fence.config.CommonFenceConfig", "defaultValue": "tcc_fence_log" }, { "name": "seata.tcc.fence.clean-period", "type": "java.time.Duration", "description": "TCC fence log clean period. only duration type format are supported.", - "sourceType": "io.seata.rm.tcc.config.TCCFenceConfig", + "sourceType": "io.seata.spring.fence.config.CommonFenceConfig", "defaultValue": "1d" }, { diff --git a/spring/pom.xml b/spring/pom.xml index c67e64c763a..540c348265a 100644 --- a/spring/pom.xml +++ b/spring/pom.xml @@ -39,11 +39,6 @@ seata-rm-datasource ${project.version} - - ${project.groupId} - seata-tcc - ${project.version} - ${project.groupId} seata-rm @@ -64,6 +59,10 @@ org.springframework spring-context + + org.springframework + spring-jdbc + org.jetbrains.kotlin kotlin-stdlib-jdk8 @@ -78,8 +77,10 @@ org.jetbrains.kotlinx kotlinx-coroutines-core - - + + com.alibaba + fastjson + diff --git a/tcc/src/main/java/io/seata/rm/tcc/api/BusinessActionContext.java b/spring/src/main/java/io/seata/rm/tcc/api/BusinessActionContext.java similarity index 93% rename from tcc/src/main/java/io/seata/rm/tcc/api/BusinessActionContext.java rename to spring/src/main/java/io/seata/rm/tcc/api/BusinessActionContext.java index f11b3249940..0676adddcf0 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/api/BusinessActionContext.java +++ b/spring/src/main/java/io/seata/rm/tcc/api/BusinessActionContext.java @@ -20,7 +20,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import io.seata.rm.tcc.interceptor.ActionContextUtil; +import io.seata.core.model.BranchType; +import io.seata.spring.interceptor.ActionContextUtil; /** * The type Business action context. @@ -50,6 +51,11 @@ public class BusinessActionContext implements Serializable { */ private Boolean isUpdated; + /** + * branch Type + */ + private BranchType branchType; + /** * action context */ @@ -219,6 +225,14 @@ public void setUpdated(Boolean updated) { isUpdated = updated; } + public BranchType getBranchType() { + return branchType; + } + + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -227,6 +241,7 @@ public String toString() { .append(",action_name:").append(actionName) .append(",is_delay_report:").append(isDelayReport) .append(",is_updated:").append(isDelayReport) + .append(",branch_type:").append(branchType) .append(",action_context:") .append(actionContext).append("]"); return sb.toString(); diff --git a/tcc/src/main/java/io/seata/rm/tcc/api/BusinessActionContextParameter.java b/spring/src/main/java/io/seata/rm/tcc/api/BusinessActionContextParameter.java similarity index 85% rename from tcc/src/main/java/io/seata/rm/tcc/api/BusinessActionContextParameter.java rename to spring/src/main/java/io/seata/rm/tcc/api/BusinessActionContextParameter.java index 518a7c5dec4..786b8a8549b 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/api/BusinessActionContextParameter.java +++ b/spring/src/main/java/io/seata/rm/tcc/api/BusinessActionContextParameter.java @@ -26,7 +26,7 @@ * add this annotation on the parameters of the try method, and the parameters will be passed to the action context * * @author zhangsen - * @see io.seata.rm.tcc.interceptor.ActionContextUtil + * @see io.seata.spring.interceptor.ActionContextUtil */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) @@ -36,7 +36,7 @@ * parameter's name. Synonym for {@link #paramName()}. * * @return the name of the param or field - * @see io.seata.rm.tcc.interceptor.ActionContextUtil#getParamNameFromAnnotation + * @see io.seata.spring.interceptor.ActionContextUtil#getParamNameFromAnnotation */ String value() default ""; @@ -44,7 +44,7 @@ * parameter's name. Synonym for {@link #value()}. * * @return the name of the param or field - * @see io.seata.rm.tcc.interceptor.ActionContextUtil#getParamNameFromAnnotation + * @see io.seata.spring.interceptor.ActionContextUtil#getParamNameFromAnnotation */ String paramName() default ""; @@ -61,7 +61,7 @@ * Specify the index of the parameter in the List * * @return the index of the List - * @see io.seata.rm.tcc.interceptor.ActionContextUtil#getByIndex + * @see io.seata.spring.interceptor.ActionContextUtil#getByIndex */ int index() default -1; @@ -70,8 +70,8 @@ * if {@code index >= 0}, the object get from the List and then do get the parameter from the property of the object * * @return the boolean - * @see io.seata.rm.tcc.interceptor.ActionContextUtil#loadParamByAnnotationAndPutToContext - * @see io.seata.rm.tcc.interceptor.ActionContextUtil#fetchContextFromObject + * @see io.seata.spring.interceptor.ActionContextUtil#loadParamByAnnotationAndPutToContext + * @see io.seata.spring.interceptor.ActionContextUtil#fetchContextFromObject */ boolean isParamInProperty() default false; } diff --git a/tcc/src/main/java/io/seata/rm/tcc/api/BusinessActionContextUtil.java b/spring/src/main/java/io/seata/rm/tcc/api/BusinessActionContextUtil.java similarity index 68% rename from tcc/src/main/java/io/seata/rm/tcc/api/BusinessActionContextUtil.java rename to spring/src/main/java/io/seata/rm/tcc/api/BusinessActionContextUtil.java index 30a52ae0500..ac5cb348eb6 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/api/BusinessActionContextUtil.java +++ b/spring/src/main/java/io/seata/rm/tcc/api/BusinessActionContextUtil.java @@ -16,17 +16,18 @@ package io.seata.rm.tcc.api; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import com.alibaba.fastjson.JSON; import io.seata.common.Constants; import io.seata.common.exception.FrameworkException; import io.seata.common.util.CollectionUtils; +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.rm.DefaultResourceManager; -import io.seata.rm.tcc.interceptor.ActionContextUtil; +import io.seata.spring.interceptor.ActionContextUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,11 +107,11 @@ public static boolean reportContext(BusinessActionContext actionContext) { try { // branch report DefaultResourceManager.get().branchReport( - BranchType.TCC, + actionContext.getBranchType(), actionContext.getXid(), actionContext.getBranchId(), BranchStatus.Registered, - JSON.toJSONString(Collections.singletonMap(Constants.TCC_ACTION_CONTEXT, actionContext.getActionContext())) + JSON.toJSONString(Collections.singletonMap(Constants.TX_ACTION_CONTEXT, actionContext.getActionContext())) ); // reset to un_updated @@ -134,4 +135,43 @@ public static void setContext(BusinessActionContext context) { public static void clear() { CONTEXT_HOLDER.remove(); } + + /** + * 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 + */ + public static BusinessActionContext getBusinessActionContext(String xid, long branchId, String resourceId, + String applicationData) { + Map actionContextMap = null; + if (StringUtils.isNotBlank(applicationData)) { + Map tccContext = JSON.parseObject(applicationData, Map.class); + actionContextMap = (Map) tccContext.get(Constants.TX_ACTION_CONTEXT); + } + if (actionContextMap == null) { + actionContextMap = new HashMap<>(2); + } + + //instance the action context + BusinessActionContext businessActionContext = new BusinessActionContext( + xid, String.valueOf(branchId), actionContextMap); + businessActionContext.setActionName(resourceId); + return businessActionContext; + } + + public static Object[] getTwoPhaseMethodParams(String[] keys, Class[] argsClasses, BusinessActionContext businessActionContext) { + Object[] args = new Object[argsClasses.length]; + for (int i = 0; i < argsClasses.length; i++) { + if (argsClasses[i].equals(BusinessActionContext.class)) { + args[i] = businessActionContext; + } else { + args[i] = businessActionContext.getActionContext(keys[i], argsClasses[i]); + } + } + return args; + } } diff --git a/tcc/src/main/java/io/seata/rm/tcc/api/ParamType.java b/spring/src/main/java/io/seata/rm/tcc/api/ParamType.java similarity index 100% rename from tcc/src/main/java/io/seata/rm/tcc/api/ParamType.java rename to spring/src/main/java/io/seata/rm/tcc/api/ParamType.java diff --git a/spring/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java b/spring/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java index 852d959e55e..aaf85fa2e49 100644 --- a/spring/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java +++ b/spring/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java @@ -37,10 +37,12 @@ import io.seata.core.rpc.netty.TmNettyRemotingClient; import io.seata.rm.RMClient; import io.seata.spring.annotation.scannercheckers.PackageScannerChecker; -import io.seata.spring.tcc.TccActionInterceptor; +import io.seata.spring.autoproxy.DefaultTransactionAutoProxy; +import io.seata.spring.autoproxy.IsTransactionProxyResult; +import io.seata.spring.interceptor.TxBeanParserUtils; +import io.seata.spring.remoting.parser.LocalServiceRemotingParser; import io.seata.spring.util.OrderUtil; import io.seata.spring.util.SpringProxyUtils; -import io.seata.spring.util.TCCBeanParserUtils; import io.seata.tm.TMClient; import io.seata.tm.api.FailureHandler; import org.aopalliance.aop.Advice; @@ -259,9 +261,18 @@ private void registerSpringShutdownHook() { * TCC mode: * @see io.seata.rm.tcc.api.LocalTCC // TCC annotation on interface * @see io.seata.rm.tcc.api.TwoPhaseBusinessAction // TCC annotation on try method - * @see io.seata.rm.tcc.remoting.RemotingParser // Remote TCC service parser + * @see io.seata.spring.remoting.RemotingParser // Remote TCC service parser * Corresponding interceptor: - * @see io.seata.spring.tcc.TccActionInterceptor // the interceptor of TCC mode + * @see io.seata.rm.tcc.interceptor.TccActionInterceptor // the interceptor of TCC mode + * + * SAGA annotation mode: + * @see LocalService // local service annotation on interface + * @see io.seata.saga.api.SagaTransactional // SAGA annotation on commit method + * @see LocalServiceRemotingParser // Remote SAGA service parser + * @see io.seata.saga.autoproxy.SagaTransactionAutoProxy // SAGA transaction auto proxy + * Corresponding interceptor: + * @see io.seata.saga.interceptor.SagaActionInterceptor // the interceptor of SAGA annotation mode + * */ @Override protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { @@ -276,14 +287,15 @@ protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) return bean; } interceptor = null; - //check TCC proxy - if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) { + //check Transaction proxy + if (TxBeanParserUtils.isTxAutoProxy(bean, beanName, applicationContext)) { + IsTransactionProxyResult isTransactionProxyResult = DefaultTransactionAutoProxy.get().getIsProxyTargetBeanResult(beanName); // init tcc fence clean task if enable useTccFence - TCCBeanParserUtils.initTccFenceCleanTask(TCCBeanParserUtils.getRemotingDesc(beanName), applicationContext); - //TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC - interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName)); + TxBeanParserUtils.initCommonFenceCleanTask(TxBeanParserUtils.getRemotingDesc(beanName), applicationContext, isTransactionProxyResult.isUseCommonFence()); + //transaction interceptor(TCC/SAGA), proxy bean of sofa:reference/dubbo:reference, LocalTCC and LocalService + interceptor = isTransactionProxyResult.getMethodInterceptor(); ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, - (ConfigurationChangeListener)interceptor); + (ConfigurationChangeListener) interceptor); } else { Class serviceInterface = SpringProxyUtils.findTargetClass(bean); Class[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean); diff --git a/spring/src/main/java/io/seata/spring/annotation/GlobalTransactional.java b/spring/src/main/java/io/seata/spring/annotation/GlobalTransactional.java index 2fb478073e9..40ff30dad0c 100644 --- a/spring/src/main/java/io/seata/spring/annotation/GlobalTransactional.java +++ b/spring/src/main/java/io/seata/spring/annotation/GlobalTransactional.java @@ -40,7 +40,7 @@ * @see io.seata.spring.annotation.datasource.SeataAutoDataSourceProxyAdvice#invoke(MethodInvocation) io.seata.spring * .annotation.datasource.SeataAutoDataSourceProxyAdvice#invoke(MethodInvocation)// RM: the interceptor of * GlobalLockLogic and AT/XA mode - * @see io.seata.spring.tcc.TccActionInterceptor#invoke(MethodInvocation) io.seata.spring.tcc + * @see io.seata.rm.tcc.interceptor.TccActionInterceptor#invoke(MethodInvocation) io.seata.spring.tcc * .TccActionInterceptor#invoke(MethodInvocation)// RM: the interceptor of TCC mode */ @Retention(RetentionPolicy.RUNTIME) diff --git a/spring/src/main/java/io/seata/spring/annotation/LocalService.java b/spring/src/main/java/io/seata/spring/annotation/LocalService.java new file mode 100644 index 00000000000..d2ac7db7897 --- /dev/null +++ b/spring/src/main/java/io/seata/spring/annotation/LocalService.java @@ -0,0 +1,37 @@ +/* + * 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.spring.annotation; + +import io.seata.spring.remoting.parser.LocalServiceRemotingParser; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; + +/** + * Local transaction bean annotation, add on the TCC/SAGA interface + * + * @author ruishansun + * @see io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary(Object, String, Object) // the scanner for TM, GlobalLock, TCC mode and SAGA mode + * @see LocalServiceRemotingParser // the RemotingParser impl for Local transaction + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface LocalService { +} diff --git a/spring/src/main/java/io/seata/spring/autoproxy/DefaultTransactionAutoProxy.java b/spring/src/main/java/io/seata/spring/autoproxy/DefaultTransactionAutoProxy.java new file mode 100644 index 00000000000..60e3bab6fc1 --- /dev/null +++ b/spring/src/main/java/io/seata/spring/autoproxy/DefaultTransactionAutoProxy.java @@ -0,0 +1,101 @@ +/* + * 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.spring.autoproxy; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; +import io.seata.spring.remoting.RemotingDesc; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * the default transaction auto proxy + * + * @author ruishansun + */ +public class DefaultTransactionAutoProxy { + + /** + * all the transaction auto proxy + */ + protected static final List ALL_TRANSACTION_AUTO_PROXIES = new ArrayList<>(); + /** + * method interceptor map, beanName -> IsTransactionProxyResult + */ + private static final Map METHOD_INTERCEPTOR_MAP = new ConcurrentHashMap<>(); + + private static class SingletonHolder { + private static final DefaultTransactionAutoProxy INSTANCE = new DefaultTransactionAutoProxy(); + } + + /** + * Get the default transaction auto proxy + * + * @return the default transaction auto proxy + */ + public static DefaultTransactionAutoProxy get() { + return SingletonHolder.INSTANCE; + } + + /** + * Instantiates a new default transaction auto proxy + */ + protected DefaultTransactionAutoProxy() { + initTransactionAutoProxy(); + } + + /** + * init transaction auto proxy + */ + private void initTransactionAutoProxy() { + List proxies = EnhancedServiceLoader.loadAll(TransactionAutoProxy.class); + if (CollectionUtils.isNotEmpty(proxies)) { + ALL_TRANSACTION_AUTO_PROXIES.addAll(proxies); + } + } + + /** + * whether is transaction auto proxy + * + * @param beanName the beanName + * @param remotingDesc the remotingDesc + * @return true or false + */ + public boolean isTransactionAutoProxy(String beanName, RemotingDesc remotingDesc) { + for (TransactionAutoProxy proxy : ALL_TRANSACTION_AUTO_PROXIES) { + IsTransactionProxyResult result = proxy.isTransactionProxyTargetBean(remotingDesc); + if (result.isProxyTargetBean()) { + METHOD_INTERCEPTOR_MAP.put(beanName, result); + return true; + } + } + return false; + } + + /** + * get the IsTransactionProxyResult + * + * @param beanName the beanName + * @return the IsTransactionProxyResult + */ + public IsTransactionProxyResult getIsProxyTargetBeanResult(String beanName) { + IsTransactionProxyResult result = METHOD_INTERCEPTOR_MAP.get(beanName); + return result != null ? result : new IsTransactionProxyResult(); + } +} diff --git a/spring/src/main/java/io/seata/spring/autoproxy/IsTransactionProxyResult.java b/spring/src/main/java/io/seata/spring/autoproxy/IsTransactionProxyResult.java new file mode 100644 index 00000000000..07d831a749e --- /dev/null +++ b/spring/src/main/java/io/seata/spring/autoproxy/IsTransactionProxyResult.java @@ -0,0 +1,65 @@ +/* + * 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.spring.autoproxy; + +import org.aopalliance.intercept.MethodInterceptor; + +/** + * whether is the transaction proxy result + * + * @author ruishansun + */ +public class IsTransactionProxyResult { + + /** + * whether proxied by transaction bean + */ + private boolean isProxyTargetBean; + + /** + * whether used common fence + */ + private boolean useCommonFence; + + /** + * transaction proxy method + */ + private MethodInterceptor methodInterceptor; + + public boolean isProxyTargetBean() { + return isProxyTargetBean; + } + + public void setProxyTargetBean(boolean proxyTargetBean) { + isProxyTargetBean = proxyTargetBean; + } + + public boolean isUseCommonFence() { + return useCommonFence; + } + + public void setUseCommonFence(boolean useCommonFence) { + this.useCommonFence = useCommonFence; + } + + public MethodInterceptor getMethodInterceptor() { + return methodInterceptor; + } + + public void setMethodInterceptor(MethodInterceptor methodInterceptor) { + this.methodInterceptor = methodInterceptor; + } +} diff --git a/spring/src/main/java/io/seata/spring/autoproxy/TransactionAutoProxy.java b/spring/src/main/java/io/seata/spring/autoproxy/TransactionAutoProxy.java new file mode 100644 index 00000000000..1f54cf24992 --- /dev/null +++ b/spring/src/main/java/io/seata/spring/autoproxy/TransactionAutoProxy.java @@ -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.spring.autoproxy; + +import io.seata.spring.remoting.RemotingDesc; + +/** + * The interface Transaction Auto Proxy. Proxied by tcc/saga with SPI. + * + * @author ruishansun + */ +public interface TransactionAutoProxy { + + /** + * Whether it is transaction auto proxy? (tcc or saga) + * + * @param remotingDesc the remotingDesc + * @return the IsTransactionProxyResult + */ + IsTransactionProxyResult isTransactionProxyTargetBean(RemotingDesc remotingDesc); +} diff --git a/tcc/src/main/java/io/seata/rm/tcc/TCCFenceHandler.java b/spring/src/main/java/io/seata/spring/fence/CommonFenceHandler.java similarity index 73% rename from tcc/src/main/java/io/seata/rm/tcc/TCCFenceHandler.java rename to spring/src/main/java/io/seata/spring/fence/CommonFenceHandler.java index f5a4dee1021..e9240e98c4d 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/TCCFenceHandler.java +++ b/spring/src/main/java/io/seata/spring/fence/CommonFenceHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc; +package io.seata.spring.fence; import java.lang.reflect.Method; import java.sql.Connection; @@ -28,11 +28,12 @@ import io.seata.common.exception.SkipCallbackWrapperException; import io.seata.common.executor.Callback; import io.seata.common.thread.NamedThreadFactory; -import io.seata.rm.tcc.constant.TCCFenceConstant; -import io.seata.rm.tcc.exception.TCCFenceException; -import io.seata.rm.tcc.store.TCCFenceDO; -import io.seata.rm.tcc.store.TCCFenceStore; -import io.seata.rm.tcc.store.db.TCCFenceStoreDataBaseDAO; +import io.seata.spring.fence.constant.CommonFenceConstant; +import io.seata.spring.fence.exception.CommonFenceException; +import io.seata.spring.fence.store.CommonFenceDO; +import io.seata.spring.fence.store.CommonFenceStore; +import io.seata.spring.fence.store.db.CommonFenceStoreDataBaseDAO; +import io.seata.spring.remoting.TwoPhaseResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.DataSourceUtils; @@ -40,19 +41,19 @@ import org.springframework.transaction.support.TransactionTemplate; /** - * TCC Fence Handler(idempotent, non_rollback, suspend) + * Common Fence Handler(idempotent, non_rollback, suspend) * * @author kaka2code */ -public class TCCFenceHandler { +public class CommonFenceHandler { - private TCCFenceHandler() { + private CommonFenceHandler() { throw new IllegalStateException("Utility class"); } - private static final Logger LOGGER = LoggerFactory.getLogger(TCCFenceHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(CommonFenceHandler.class); - private static final TCCFenceStore TCC_FENCE_DAO = TCCFenceStoreDataBaseDAO.getInstance(); + private static final CommonFenceStore COMMON_FENCE_DAO = CommonFenceStoreDataBaseDAO.getInstance(); private static DataSource dataSource; @@ -77,15 +78,15 @@ private TCCFenceHandler() { } public static void setDataSource(DataSource dataSource) { - TCCFenceHandler.dataSource = dataSource; + CommonFenceHandler.dataSource = dataSource; } public static void setTransactionTemplate(TransactionTemplate transactionTemplate) { - TCCFenceHandler.transactionTemplate = transactionTemplate; + CommonFenceHandler.transactionTemplate = transactionTemplate; } /** - * tcc prepare method enhanced + * common prepare method enhanced * * @param xid the global transaction id * @param branchId the branch transaction id @@ -97,15 +98,15 @@ public static Object prepareFence(String xid, Long branchId, String actionName, return transactionTemplate.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); - boolean result = insertTCCFenceLog(conn, xid, branchId, actionName, TCCFenceConstant.STATUS_TRIED); - LOGGER.info("TCC fence prepare result: {}. xid: {}, branchId: {}", result, xid, branchId); + boolean result = insertCommonFenceLog(conn, xid, branchId, actionName, CommonFenceConstant.STATUS_TRIED); + LOGGER.info("Common fence prepare result: {}. xid: {}, branchId: {}", result, xid, branchId); if (result) { return targetCallback.execute(); } else { - throw new TCCFenceException(String.format("Insert tcc fence record error, prepare fence failed. xid= %s, branchId= %s", xid, branchId), + throw new CommonFenceException(String.format("Insert common fence record error, prepare fence failed. xid= %s, branchId= %s", xid, branchId), FrameworkErrorCode.InsertRecordError); } - } catch (TCCFenceException e) { + } catch (CommonFenceException e) { if (e.getErrcode() == FrameworkErrorCode.DuplicateKeyException) { LOGGER.error("Branch transaction has already rollbacked before,prepare fence failed. xid= {},branchId = {}", xid, branchId); addToLogCleanQueue(xid, branchId); @@ -120,10 +121,10 @@ public static Object prepareFence(String xid, Long branchId, String actionName, } /** - * tcc commit method enhanced + * common commit method enhanced * * @param commitMethod commit method - * @param targetTCCBean target tcc bean + * @param targetTCCBean target common bean * @param xid the global transaction id * @param branchId the branch transaction id * @param args commit method's parameters @@ -134,22 +135,22 @@ public static boolean commitFence(Method commitMethod, Object targetTCCBean, return transactionTemplate.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); - TCCFenceDO tccFenceDO = TCC_FENCE_DAO.queryTCCFenceDO(conn, xid, branchId); - if (tccFenceDO == null) { - throw new TCCFenceException(String.format("TCC fence record not exists, commit fence method failed. xid= %s, branchId= %s", xid, branchId), + CommonFenceDO commonFenceDO = COMMON_FENCE_DAO.queryCommonFenceDO(conn, xid, branchId); + if (commonFenceDO == null) { + throw new CommonFenceException(String.format("Common fence record not exists, commit fence method failed. xid= %s, branchId= %s", xid, branchId), FrameworkErrorCode.RecordAlreadyExists); } - if (TCCFenceConstant.STATUS_COMMITTED == tccFenceDO.getStatus()) { - LOGGER.info("Branch transaction has already committed before. idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus()); + if (CommonFenceConstant.STATUS_COMMITTED == commonFenceDO.getStatus()) { + LOGGER.info("Branch transaction has already committed before. idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, commonFenceDO.getStatus()); return true; } - if (TCCFenceConstant.STATUS_ROLLBACKED == tccFenceDO.getStatus() || TCCFenceConstant.STATUS_SUSPENDED == tccFenceDO.getStatus()) { + if (CommonFenceConstant.STATUS_ROLLBACKED == commonFenceDO.getStatus() || CommonFenceConstant.STATUS_SUSPENDED == commonFenceDO.getStatus()) { if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus()); + LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, commonFenceDO.getStatus()); } return false; } - return updateStatusAndInvokeTargetMethod(conn, commitMethod, targetTCCBean, xid, branchId, TCCFenceConstant.STATUS_COMMITTED, status, args); + return updateStatusAndInvokeTargetMethod(conn, commitMethod, targetTCCBean, xid, branchId, CommonFenceConstant.STATUS_COMMITTED, status, args); } catch (Throwable t) { status.setRollbackOnly(); throw new SkipCallbackWrapperException(t); @@ -158,7 +159,7 @@ public static boolean commitFence(Method commitMethod, Object targetTCCBean, } /** - * tcc rollback method enhanced + * Common rollback method enhanced * * @param rollbackMethod rollback method * @param targetTCCBean target tcc bean @@ -173,29 +174,29 @@ public static boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, return transactionTemplate.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); - TCCFenceDO tccFenceDO = TCC_FENCE_DAO.queryTCCFenceDO(conn, xid, branchId); + CommonFenceDO commonFenceDO = COMMON_FENCE_DAO.queryCommonFenceDO(conn, xid, branchId); // non_rollback - if (tccFenceDO == null) { - boolean result = insertTCCFenceLog(conn, xid, branchId, actionName, TCCFenceConstant.STATUS_SUSPENDED); - LOGGER.info("Insert tcc fence record result: {}. xid: {}, branchId: {}", result, xid, branchId); + if (commonFenceDO == null) { + boolean result = insertCommonFenceLog(conn, xid, branchId, actionName, CommonFenceConstant.STATUS_SUSPENDED); + LOGGER.info("Insert common fence record result: {}. xid: {}, branchId: {}", result, xid, branchId); if (!result) { - throw new TCCFenceException(String.format("Insert tcc fence record error, rollback fence method failed. xid= %s, branchId= %s", xid, branchId), + throw new CommonFenceException(String.format("Insert common fence record error, rollback fence method failed. xid= %s, branchId= %s", xid, branchId), FrameworkErrorCode.InsertRecordError); } return true; } else { - if (TCCFenceConstant.STATUS_ROLLBACKED == tccFenceDO.getStatus() || TCCFenceConstant.STATUS_SUSPENDED == tccFenceDO.getStatus()) { - LOGGER.info("Branch transaction had already rollbacked before, idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus()); + if (CommonFenceConstant.STATUS_ROLLBACKED == commonFenceDO.getStatus() || CommonFenceConstant.STATUS_SUSPENDED == commonFenceDO.getStatus()) { + LOGGER.info("Branch transaction had already rollbacked before, idempotency rejected. xid: {}, branchId: {}, status: {}", xid, branchId, commonFenceDO.getStatus()); return true; } - if (TCCFenceConstant.STATUS_COMMITTED == tccFenceDO.getStatus()) { + if (CommonFenceConstant.STATUS_COMMITTED == commonFenceDO.getStatus()) { if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, tccFenceDO.getStatus()); + LOGGER.warn("Branch transaction status is unexpected. xid: {}, branchId: {}, status: {}", xid, branchId, commonFenceDO.getStatus()); } return false; } } - return updateStatusAndInvokeTargetMethod(conn, rollbackMethod, targetTCCBean, xid, branchId, TCCFenceConstant.STATUS_ROLLBACKED, status, args); + return updateStatusAndInvokeTargetMethod(conn, rollbackMethod, targetTCCBean, xid, branchId, CommonFenceConstant.STATUS_ROLLBACKED, status, args); } catch (Throwable t) { status.setRollbackOnly(); throw new SkipCallbackWrapperException(t); @@ -204,7 +205,7 @@ public static boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, } /** - * Insert TCC fence log + * Insert Common fence log * * @param conn the db connection * @param xid the xid @@ -212,13 +213,13 @@ public static boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, * @param status the status * @return the boolean */ - private static boolean insertTCCFenceLog(Connection conn, String xid, Long branchId, String actionName, Integer status) { - TCCFenceDO tccFenceDO = new TCCFenceDO(); - tccFenceDO.setXid(xid); - tccFenceDO.setBranchId(branchId); - tccFenceDO.setActionName(actionName); - tccFenceDO.setStatus(status); - return TCC_FENCE_DAO.insertTCCFenceDO(conn, tccFenceDO); + private static boolean insertCommonFenceLog(Connection conn, String xid, Long branchId, String actionName, Integer status) { + CommonFenceDO commonFenceDO = new CommonFenceDO(); + commonFenceDO.setXid(xid); + commonFenceDO.setBranchId(branchId); + commonFenceDO.setActionName(actionName); + commonFenceDO.setStatus(status); + return COMMON_FENCE_DAO.insertCommonFenceDO(conn, commonFenceDO); } /** @@ -235,7 +236,7 @@ private static boolean updateStatusAndInvokeTargetMethod(Connection conn, Method String xid, Long branchId, int status, TransactionStatus transactionStatus, Object[] args) throws Exception { - boolean result = TCC_FENCE_DAO.updateTCCFenceDO(conn, xid, branchId, status, TCCFenceConstant.STATUS_TRIED); + boolean result = COMMON_FENCE_DAO.updateCommonFenceDO(conn, xid, branchId, status, CommonFenceConstant.STATUS_TRIED); if (result) { // invoke two phase method Object ret = method.invoke(targetTCCBean, args); @@ -255,7 +256,7 @@ private static boolean updateStatusAndInvokeTargetMethod(Connection conn, Method } /** - * Delete TCC Fence + * Delete Common Fence * * @param xid the global transaction id * @param branchId the branch transaction id @@ -266,7 +267,7 @@ public static boolean deleteFence(String xid, Long branchId) { boolean ret = false; try { Connection conn = DataSourceUtils.getConnection(dataSource); - ret = TCC_FENCE_DAO.deleteTCCFenceDO(conn, xid, branchId); + ret = COMMON_FENCE_DAO.deleteCommonFenceDO(conn, xid, branchId); } catch (RuntimeException e) { status.setRollbackOnly(); LOGGER.error("delete fence log failed, xid: {}, branchId: {}", xid, branchId, e); @@ -276,7 +277,7 @@ public static boolean deleteFence(String xid, Long branchId) { } /** - * Delete TCC Fence By Datetime + * Delete Common Fence By Datetime * * @param datetime datetime * @return the deleted row count @@ -285,7 +286,7 @@ public static int deleteFenceByDate(Date datetime) { return transactionTemplate.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); - return TCC_FENCE_DAO.deleteTCCFenceDOByDate(conn, datetime); + return COMMON_FENCE_DAO.deleteCommonFenceDOByDate(conn, datetime); } catch (RuntimeException e) { status.setRollbackOnly(); throw e; @@ -316,7 +317,7 @@ private static void addToLogCleanQueue(final String xid, final long branchId) { /** * clean fence log that has the final status runnable. * - * @see TCCFenceConstant + * @see CommonFenceConstant */ private static class FenceLogCleanRunnable implements Runnable { @Override @@ -325,7 +326,7 @@ public void run() { try { FenceLogIdentity logIdentity = LOG_QUEUE.take(); - boolean ret = TCCFenceHandler.deleteFence(logIdentity.getXid(), logIdentity.getBranchId()); + boolean ret = CommonFenceHandler.deleteFence(logIdentity.getXid(), logIdentity.getBranchId()); if (!ret) { LOGGER.error("delete fence log failed, xid: {}, branchId: {}", logIdentity.getXid(), logIdentity.getBranchId()); } diff --git a/tcc/src/main/java/io/seata/rm/tcc/config/TCCFenceConfig.java b/spring/src/main/java/io/seata/spring/fence/config/CommonFenceConfig.java similarity index 75% rename from tcc/src/main/java/io/seata/rm/tcc/config/TCCFenceConfig.java rename to spring/src/main/java/io/seata/spring/fence/config/CommonFenceConfig.java index fffc08ab993..5b8037e9d69 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/config/TCCFenceConfig.java +++ b/spring/src/main/java/io/seata/spring/fence/config/CommonFenceConfig.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.config; +package io.seata.spring.fence.config; import java.time.Duration; import java.util.Date; @@ -27,9 +27,9 @@ import io.seata.common.exception.FrameworkErrorCode; import io.seata.common.thread.NamedThreadFactory; import io.seata.core.rpc.Disposable; -import io.seata.rm.tcc.TCCFenceHandler; -import io.seata.rm.tcc.exception.TCCFenceException; -import io.seata.rm.tcc.store.db.TCCFenceStoreDataBaseDAO; +import io.seata.spring.fence.CommonFenceHandler; +import io.seata.spring.fence.exception.CommonFenceException; +import io.seata.spring.fence.store.db.CommonFenceStoreDataBaseDAO; import org.apache.commons.lang.time.DateUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,48 +38,48 @@ import org.springframework.transaction.support.TransactionTemplate; /** - * TCC Fence Config + * Common Fence Config * * @author kaka2code */ -public class TCCFenceConfig implements InitializingBean, Disposable { +public class CommonFenceConfig implements InitializingBean, Disposable { - private static final Logger LOGGER = LoggerFactory.getLogger(TCCFenceConfig.class); + private static final Logger LOGGER = LoggerFactory.getLogger(CommonFenceConfig.class); private final AtomicBoolean initialized = new AtomicBoolean(false); /** - * TCC fence clean period max value. maximum interval is 68 years + * Common fence clean period max value. maximum interval is 68 years */ private static final Duration MAX_PERIOD = Duration.ofSeconds(Integer.MAX_VALUE); /** - * TCC fence clean period. only duration type format are supported + * Common fence clean period. only duration type format are supported */ - private Duration cleanPeriod = Duration.ofDays(DefaultValues.DEFAULT_TCC_FENCE_CLEAN_PERIOD); + private Duration cleanPeriod = Duration.ofDays(DefaultValues.DEFAULT_COMMON_FENCE_CLEAN_PERIOD); /** - * TCC fence log table name + * Common fence log table name */ - private String logTableName = DefaultValues.DEFAULT_TCC_FENCE_LOG_TABLE_NAME; + private String logTableName = DefaultValues.DEFAULT_COMMON_FENCE_LOG_TABLE_NAME; /** - * TCC fence datasource + * Common fence datasource */ private final DataSource dataSource; /** - * TCC fence transactionManager + * Common fence transactionManager */ private final PlatformTransactionManager transactionManager; /** - * TCC fence clean scheduled thread pool + * Common fence clean scheduled thread pool */ private final ScheduledThreadPoolExecutor tccFenceClean = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("tccFenceClean", 1)); - public TCCFenceConfig(final DataSource dataSource, final PlatformTransactionManager transactionManager) { + public CommonFenceConfig(final DataSource dataSource, final PlatformTransactionManager transactionManager) { this.dataSource = dataSource; this.transactionManager = transactionManager; } @@ -116,7 +116,7 @@ public void initCleanTask() { Date timeBefore = null; try { timeBefore = DateUtils.addSeconds(new Date(), -(int)periodSeconds); - int deletedRowCount = TCCFenceHandler.deleteFenceByDate(timeBefore); + int deletedRowCount = CommonFenceHandler.deleteFenceByDate(timeBefore); if (deletedRowCount > 0) { LOGGER.info("TCC fence clean task executed success, timeBefore: {}, deleted row count: {}", timeBefore, deletedRowCount); @@ -141,19 +141,19 @@ public void destroy() { public void afterPropertiesSet() { // set log table name if (logTableName != null) { - TCCFenceStoreDataBaseDAO.getInstance().setLogTableName(logTableName); + CommonFenceStoreDataBaseDAO.getInstance().setLogTableName(logTableName); } if (dataSource != null) { // set dataSource - TCCFenceHandler.setDataSource(dataSource); + CommonFenceHandler.setDataSource(dataSource); } else { - throw new TCCFenceException(FrameworkErrorCode.DateSourceNeedInjected); + throw new CommonFenceException(FrameworkErrorCode.DateSourceNeedInjected); } if (transactionManager != null) { // set transaction template - TCCFenceHandler.setTransactionTemplate(new TransactionTemplate(transactionManager)); + CommonFenceHandler.setTransactionTemplate(new TransactionTemplate(transactionManager)); } else { - throw new TCCFenceException(FrameworkErrorCode.TransactionManagerNeedInjected); + throw new CommonFenceException(FrameworkErrorCode.TransactionManagerNeedInjected); } } } diff --git a/tcc/src/main/java/io/seata/rm/tcc/constant/TCCFenceConstant.java b/spring/src/main/java/io/seata/spring/fence/constant/CommonFenceConstant.java similarity index 89% rename from tcc/src/main/java/io/seata/rm/tcc/constant/TCCFenceConstant.java rename to spring/src/main/java/io/seata/spring/fence/constant/CommonFenceConstant.java index 7c9a2b416cf..bc56d841559 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/constant/TCCFenceConstant.java +++ b/spring/src/main/java/io/seata/spring/fence/constant/CommonFenceConstant.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.constant; +package io.seata.spring.fence.constant; /** - * TCC Fence Constant + * Common Fence Constant * * @author kaka2code */ -public class TCCFenceConstant { +public class CommonFenceConstant { - private TCCFenceConstant() { + private CommonFenceConstant() { throw new IllegalStateException("Utility class"); } diff --git a/tcc/src/main/java/io/seata/rm/tcc/exception/TCCFenceException.java b/spring/src/main/java/io/seata/spring/fence/exception/CommonFenceException.java similarity index 65% rename from tcc/src/main/java/io/seata/rm/tcc/exception/TCCFenceException.java rename to spring/src/main/java/io/seata/spring/fence/exception/CommonFenceException.java index 716da66ed1f..0200dd993c5 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/exception/TCCFenceException.java +++ b/spring/src/main/java/io/seata/spring/fence/exception/CommonFenceException.java @@ -13,39 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.exception; +package io.seata.spring.fence.exception; import io.seata.common.exception.FrameworkErrorCode; import io.seata.common.exception.FrameworkException; /** - * TCC Fence Exception + * Common Fence Exception * * @author kaka2code */ -public class TCCFenceException extends FrameworkException { +public class CommonFenceException extends FrameworkException { - public TCCFenceException(FrameworkErrorCode err) { + public CommonFenceException(FrameworkErrorCode err) { super(err); } - public TCCFenceException(String msg) { + public CommonFenceException(String msg) { super(msg); } - public TCCFenceException(String msg, FrameworkErrorCode errCode) { + public CommonFenceException(String msg, FrameworkErrorCode errCode) { super(msg, errCode); } - public TCCFenceException(Throwable cause, String msg, FrameworkErrorCode errCode) { + public CommonFenceException(Throwable cause, String msg, FrameworkErrorCode errCode) { super(cause, msg, errCode); } - public TCCFenceException(Throwable th) { + public CommonFenceException(Throwable th) { super(th); } - public TCCFenceException(Throwable th, String msg) { + public CommonFenceException(Throwable th, String msg) { super(th, msg); } diff --git a/tcc/src/main/java/io/seata/rm/tcc/store/TCCFenceDO.java b/spring/src/main/java/io/seata/spring/fence/store/CommonFenceDO.java similarity index 94% rename from tcc/src/main/java/io/seata/rm/tcc/store/TCCFenceDO.java rename to spring/src/main/java/io/seata/spring/fence/store/CommonFenceDO.java index 6878a9a0c74..ba079cb955c 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/store/TCCFenceDO.java +++ b/spring/src/main/java/io/seata/spring/fence/store/CommonFenceDO.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.store; +package io.seata.spring.fence.store; import java.util.Date; /** - * TCC Fence Domain + * Common Fence Domain * * @author kaka2code */ -public class TCCFenceDO { +public class CommonFenceDO { /** * the global transaction id @@ -40,7 +40,7 @@ public class TCCFenceDO { private String actionName; /** - * the tcc fence status + * the common fence status * tried: 1; committed: 2; rollbacked: 3; suspended: 4 */ private Integer status; diff --git a/tcc/src/main/java/io/seata/rm/tcc/store/TCCFenceStore.java b/spring/src/main/java/io/seata/spring/fence/store/CommonFenceStore.java similarity index 63% rename from tcc/src/main/java/io/seata/rm/tcc/store/TCCFenceStore.java rename to spring/src/main/java/io/seata/spring/fence/store/CommonFenceStore.java index 9cbd6ba255e..ff9d04b3434 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/store/TCCFenceStore.java +++ b/spring/src/main/java/io/seata/spring/fence/store/CommonFenceStore.java @@ -13,56 +13,56 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.store; +package io.seata.spring.fence.store; import java.sql.Connection; import java.util.Date; /** - * The TCC Fence Store + * The common Fence Store * * @author kaka2code */ -public interface TCCFenceStore { +public interface CommonFenceStore { /** - * Query tcc fence do. + * Query common fence do. * @param xid the global transaction id * @param branchId the branch transaction id - * @return the tcc fence do + * @return the common fence do */ - TCCFenceDO queryTCCFenceDO(Connection conn, String xid, Long branchId); + CommonFenceDO queryCommonFenceDO(Connection conn, String xid, Long branchId); /** - * Insert tcc fence do boolean. - * @param tccFenceDO the tcc fence do + * Insert common fence do boolean. + * @param commonFenceDO the common fence do * @return the boolean */ - boolean insertTCCFenceDO(Connection conn, TCCFenceDO tccFenceDO); + boolean insertCommonFenceDO(Connection conn, CommonFenceDO commonFenceDO); /** - * Update tcc fence do boolean. + * Update common fence do boolean. * @param xid the global transaction id * @param branchId the branch transaction id * @param newStatus the new status * @return the boolean */ - boolean updateTCCFenceDO(Connection conn, String xid, Long branchId, int newStatus, int oldStatus); + boolean updateCommonFenceDO(Connection conn, String xid, Long branchId, int newStatus, int oldStatus); /** - * Delete tcc fence do boolean. + * Delete common fence do boolean. * @param xid the global transaction id * @param branchId the branch transaction id * @return the boolean */ - boolean deleteTCCFenceDO(Connection conn, String xid, Long branchId); + boolean deleteCommonFenceDO(Connection conn, String xid, Long branchId); /** - * Delete tcc fence by datetime. + * Delete common fence by datetime. * @param datetime datetime * @return the deleted row count */ - int deleteTCCFenceDOByDate(Connection conn, Date datetime); + int deleteCommonFenceDOByDate(Connection conn, Date datetime); /** * Set LogTable Name diff --git a/tcc/src/main/java/io/seata/rm/tcc/store/db/TCCFenceStoreDataBaseDAO.java b/spring/src/main/java/io/seata/spring/fence/store/db/CommonFenceStoreDataBaseDAO.java similarity index 60% rename from tcc/src/main/java/io/seata/rm/tcc/store/db/TCCFenceStoreDataBaseDAO.java rename to spring/src/main/java/io/seata/spring/fence/store/db/CommonFenceStoreDataBaseDAO.java index 10e6c2979fd..5da3f2393ae 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/store/db/TCCFenceStoreDataBaseDAO.java +++ b/spring/src/main/java/io/seata/spring/fence/store/db/CommonFenceStoreDataBaseDAO.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.store.db; +package io.seata.spring.fence.store.db; import io.seata.common.DefaultValues; import io.seata.common.exception.DataAccessException; import io.seata.common.exception.FrameworkErrorCode; import io.seata.common.exception.StoreException; import io.seata.common.util.IOUtil; -import io.seata.rm.tcc.exception.TCCFenceException; -import io.seata.rm.tcc.store.TCCFenceDO; -import io.seata.rm.tcc.store.TCCFenceStore; -import io.seata.rm.tcc.store.db.sql.TCCFenceStoreSqls; +import io.seata.spring.fence.exception.CommonFenceException; +import io.seata.spring.fence.store.CommonFenceDO; +import io.seata.spring.fence.store.CommonFenceStore; +import io.seata.spring.fence.store.db.sql.CommonFenceStoreSqls; import java.sql.Connection; import java.sql.PreparedStatement; @@ -34,26 +34,26 @@ import java.util.Date; /** - * The type TCC Fence store data base dao + * The type Common Fence store data base dao * * @author kaka2code */ -public class TCCFenceStoreDataBaseDAO implements TCCFenceStore { +public class CommonFenceStoreDataBaseDAO implements CommonFenceStore { /** - * TCC fence log table name + * Common fence log table name */ - private String logTableName = DefaultValues.DEFAULT_TCC_FENCE_LOG_TABLE_NAME; + private String logTableName = DefaultValues.DEFAULT_COMMON_FENCE_LOG_TABLE_NAME; - private static volatile TCCFenceStoreDataBaseDAO instance = null; + private static volatile CommonFenceStoreDataBaseDAO instance = null; - private TCCFenceStoreDataBaseDAO() {} + private CommonFenceStoreDataBaseDAO() {} - public static TCCFenceStore getInstance() { + public static CommonFenceStore getInstance() { if (instance == null) { - synchronized (TCCFenceStore.class) { + synchronized (CommonFenceStore.class) { if (instance == null) { - instance = new TCCFenceStoreDataBaseDAO(); + instance = new CommonFenceStoreDataBaseDAO(); } } } @@ -61,21 +61,21 @@ public static TCCFenceStore getInstance() { } @Override - public TCCFenceDO queryTCCFenceDO(Connection conn, String xid, Long branchId) { + public CommonFenceDO queryCommonFenceDO(Connection conn, String xid, Long branchId) { PreparedStatement ps = null; ResultSet rs = null; try { - String sql = TCCFenceStoreSqls.getQuerySQLByBranchIdAndXid(logTableName); + String sql = CommonFenceStoreSqls.getQuerySQLByBranchIdAndXid(logTableName); ps = conn.prepareStatement(sql); ps.setString(1, xid); ps.setLong(2, branchId); rs = ps.executeQuery(); if (rs.next()) { - TCCFenceDO tccFenceDO = new TCCFenceDO(); - tccFenceDO.setXid(rs.getString("xid")); - tccFenceDO.setBranchId(rs.getLong("branch_id")); - tccFenceDO.setStatus(rs.getInt("status")); - return tccFenceDO; + CommonFenceDO commonFenceDO = new CommonFenceDO(); + commonFenceDO.setXid(rs.getString("xid")); + commonFenceDO.setBranchId(rs.getLong("branch_id")); + commonFenceDO.setStatus(rs.getInt("status")); + return commonFenceDO; } else { return null; } @@ -87,22 +87,22 @@ public TCCFenceDO queryTCCFenceDO(Connection conn, String xid, Long branchId) { } @Override - public boolean insertTCCFenceDO(Connection conn, TCCFenceDO tccFenceDO) { + public boolean insertCommonFenceDO(Connection conn, CommonFenceDO commonFenceDO) { PreparedStatement ps = null; try { Timestamp now = new Timestamp(System.currentTimeMillis()); - String sql = TCCFenceStoreSqls.getInsertLocalTCCLogSQL(logTableName); + String sql = CommonFenceStoreSqls.getInsertLocalTCCLogSQL(logTableName); ps = conn.prepareStatement(sql); - ps.setString(1, tccFenceDO.getXid()); - ps.setLong(2, tccFenceDO.getBranchId()); - ps.setString(3, tccFenceDO.getActionName()); - ps.setInt(4, tccFenceDO.getStatus()); + ps.setString(1, commonFenceDO.getXid()); + ps.setLong(2, commonFenceDO.getBranchId()); + ps.setString(3, commonFenceDO.getActionName()); + ps.setInt(4, commonFenceDO.getStatus()); ps.setTimestamp(5, now); ps.setTimestamp(6, now); return ps.executeUpdate() > 0; } catch (SQLIntegrityConstraintViolationException e) { - throw new TCCFenceException(String.format("Insert tcc fence record duplicate key exception. xid= %s, branchId= %s", tccFenceDO.getXid(), tccFenceDO.getBranchId()), + throw new CommonFenceException(String.format("Insert tcc fence record duplicate key exception. xid= %s, branchId= %s", commonFenceDO.getXid(), commonFenceDO.getBranchId()), FrameworkErrorCode.DuplicateKeyException); } catch (SQLException e) { throw new StoreException(e); @@ -112,10 +112,10 @@ public boolean insertTCCFenceDO(Connection conn, TCCFenceDO tccFenceDO) { } @Override - public boolean updateTCCFenceDO(Connection conn, String xid, Long branchId, int newStatus, int oldStatus) { + public boolean updateCommonFenceDO(Connection conn, String xid, Long branchId, int newStatus, int oldStatus) { PreparedStatement ps = null; try { - String sql = TCCFenceStoreSqls.getUpdateStatusSQLByBranchIdAndXid(logTableName); + String sql = CommonFenceStoreSqls.getUpdateStatusSQLByBranchIdAndXid(logTableName); ps = conn.prepareStatement(sql); ps.setInt(1, newStatus); // gmt_modified @@ -132,10 +132,10 @@ public boolean updateTCCFenceDO(Connection conn, String xid, Long branchId, int } @Override - public boolean deleteTCCFenceDO(Connection conn, String xid, Long branchId) { + public boolean deleteCommonFenceDO(Connection conn, String xid, Long branchId) { PreparedStatement ps = null; try { - String sql = TCCFenceStoreSqls.getDeleteSQLByBranchIdAndXid(logTableName); + String sql = CommonFenceStoreSqls.getDeleteSQLByBranchIdAndXid(logTableName); ps = conn.prepareStatement(sql); ps.setString(1, xid); ps.setLong(2, branchId); @@ -149,10 +149,10 @@ public boolean deleteTCCFenceDO(Connection conn, String xid, Long branchId) { } @Override - public int deleteTCCFenceDOByDate(Connection conn, Date datetime) { + public int deleteCommonFenceDOByDate(Connection conn, Date datetime) { PreparedStatement ps = null; try { - String sql = TCCFenceStoreSqls.getDeleteSQLByDateAndStatus(logTableName); + String sql = CommonFenceStoreSqls.getDeleteSQLByDateAndStatus(logTableName); ps = conn.prepareStatement(sql); ps.setTimestamp(1, new Timestamp(datetime.getTime())); return ps.executeUpdate(); diff --git a/tcc/src/main/java/io/seata/rm/tcc/store/db/sql/TCCFenceStoreSqls.java b/spring/src/main/java/io/seata/spring/fence/store/db/sql/CommonFenceStoreSqls.java similarity index 89% rename from tcc/src/main/java/io/seata/rm/tcc/store/db/sql/TCCFenceStoreSqls.java rename to spring/src/main/java/io/seata/spring/fence/store/db/sql/CommonFenceStoreSqls.java index 130cb132c46..4fc94e91c84 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/store/db/sql/TCCFenceStoreSqls.java +++ b/spring/src/main/java/io/seata/spring/fence/store/db/sql/CommonFenceStoreSqls.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.store.db.sql; +package io.seata.spring.fence.store.db.sql; -import io.seata.rm.tcc.constant.TCCFenceConstant; +import io.seata.spring.fence.constant.CommonFenceConstant; /** * TCC Fence Store Sqls * * @author kaka2code */ -public class TCCFenceStoreSqls { +public class CommonFenceStoreSqls { - private TCCFenceStoreSqls() { + private CommonFenceStoreSqls() { throw new IllegalStateException("Utility class"); } @@ -63,7 +63,7 @@ private TCCFenceStoreSqls() { */ protected static final String DELETE_BY_DATE_AND_STATUS = "delete from " + LOCAL_TCC_LOG_PLACEHOLD + " where gmt_modified < ? " - + " and status in (" + TCCFenceConstant.STATUS_COMMITTED + " , " + TCCFenceConstant.STATUS_ROLLBACKED + " , " + TCCFenceConstant.STATUS_SUSPENDED + ")"; + + " and status in (" + CommonFenceConstant.STATUS_COMMITTED + " , " + CommonFenceConstant.STATUS_ROLLBACKED + " , " + CommonFenceConstant.STATUS_SUSPENDED + ")"; public static String getInsertLocalTCCLogSQL(String localTccTable) { return INSERT_LOCAL_TCC_LOG.replace(LOCAL_TCC_LOG_PLACEHOLD, localTccTable); diff --git a/tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionContextFilter.java b/spring/src/main/java/io/seata/spring/interceptor/ActionContextFilter.java similarity index 96% rename from tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionContextFilter.java rename to spring/src/main/java/io/seata/spring/interceptor/ActionContextFilter.java index 32a6f492161..e0f86ad70a9 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionContextFilter.java +++ b/spring/src/main/java/io/seata/spring/interceptor/ActionContextFilter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.interceptor; +package io.seata.spring.interceptor; import io.seata.rm.tcc.api.BusinessActionContextParameter; diff --git a/tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionContextUtil.java b/spring/src/main/java/io/seata/spring/interceptor/ActionContextUtil.java similarity index 99% rename from tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionContextUtil.java rename to spring/src/main/java/io/seata/spring/interceptor/ActionContextUtil.java index 8b50b52bce9..4cc9ee1085c 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionContextUtil.java +++ b/spring/src/main/java/io/seata/spring/interceptor/ActionContextUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.interceptor; +package io.seata.spring.interceptor; import java.lang.reflect.Field; import java.util.Collections; diff --git a/tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionInterceptorHandler.java b/spring/src/main/java/io/seata/spring/interceptor/ActionInterceptorHandler.java similarity index 63% rename from tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionInterceptorHandler.java rename to spring/src/main/java/io/seata/spring/interceptor/ActionInterceptorHandler.java index ea80e3e4d62..23a4c662ed4 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/interceptor/ActionInterceptorHandler.java +++ b/spring/src/main/java/io/seata/spring/interceptor/ActionInterceptorHandler.java @@ -13,15 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.interceptor; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.UndeclaredThrowableException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import javax.annotation.Nonnull; +package io.seata.spring.interceptor; import com.alibaba.fastjson.JSON; import io.seata.common.Constants; @@ -31,20 +23,31 @@ import io.seata.common.util.CollectionUtils; 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.TCCFenceHandler; import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.BusinessActionContextUtil; import io.seata.rm.tcc.api.ParamType; -import io.seata.rm.tcc.api.TwoPhaseBusinessAction; +import io.seata.spring.fence.CommonFenceHandler; +import io.seata.spring.remoting.RemotingDesc; +import io.seata.spring.util.DubboUtil; +import io.seata.spring.util.SpringProxyUtils; +import org.aopalliance.intercept.MethodInvocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + /** - * Handler the TCC Participant Aspect : Setting Context, Creating Branch Record + * Handler the Tx Participant Aspect : Setting Context, Creating Branch Record * * @author zhangsen */ @@ -53,30 +56,32 @@ public class ActionInterceptorHandler { private static final Logger LOGGER = LoggerFactory.getLogger(ActionInterceptorHandler.class); /** - * Handler the TCC Aspect + * Handler the Tx Aspect * - * @param method the method - * @param arguments the arguments - * @param businessAction the business action - * @param targetCallback the target callback + * @param method the method + * @param arguments the arguments + * @param businessActionParam the business action params + * @param targetCallback the target callback * @return the business result * @throws Throwable the throwable */ - public Object proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction, - Callback targetCallback) throws Throwable { + public Object proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessActionParam businessActionParam, + Callback targetCallback) throws Throwable { //Get action context from arguments, or create a new one and then reset to arguments BusinessActionContext actionContext = getOrCreateActionContextAndResetToArguments(method.getParameterTypes(), arguments); //Set the xid actionContext.setXid(xid); //Set the action name - String actionName = businessAction.name(); + String actionName = businessActionParam.getActionName(); actionContext.setActionName(actionName); //Set the delay report - actionContext.setDelayReport(businessAction.isDelayReport()); + actionContext.setDelayReport(businessActionParam.getDelayReport()); + //Set branch type + actionContext.setBranchType(businessActionParam.getBranchType()); //Creating Branch Record - String branchId = doTccActionLogStore(method, arguments, businessAction, actionContext); + String branchId = doTxActionLogStore(method, arguments, businessActionParam, actionContext); actionContext.setBranchId(branchId); //MDC put branchId MDC.put(RootContext.MDC_KEY_BRANCH_ID, branchId); @@ -87,14 +92,14 @@ public Object proceed(Method method, Object[] arguments, String xid, TwoPhaseBus //share actionContext implicitly BusinessActionContextUtil.setContext(actionContext); - if (businessAction.useTCCFence()) { + if (businessActionParam.getUseCommonFence()) { try { - // Use TCC Fence, and return the business result - return TCCFenceHandler.prepareFence(xid, Long.valueOf(branchId), actionName, targetCallback); + // Use common Fence, and return the business result + return CommonFenceHandler.prepareFence(xid, Long.valueOf(branchId), actionName, targetCallback); } catch (SkipCallbackWrapperException | UndeclaredThrowableException e) { Throwable originException = e.getCause(); if (originException instanceof FrameworkException) { - LOGGER.error("[{}] prepare TCC fence error: {}", xid, originException.getMessage()); + LOGGER.error("[{}] prepare common fence error: {}", xid, originException.getMessage()); } throw originException; } @@ -133,7 +138,7 @@ protected BusinessActionContext getOrCreateActionContextAndResetToArguments(Clas int argIndex = 0; for (Class parameterType : parameterTypes) { if (BusinessActionContext.class.isAssignableFrom(parameterType)) { - actionContext = (BusinessActionContext)arguments[argIndex]; + actionContext = (BusinessActionContext) arguments[argIndex]; if (actionContext == null) { // If the action context exists in arguments but is null, create a new one and reset the action context to the arguments actionContext = new BusinessActionContext(); @@ -157,14 +162,14 @@ protected BusinessActionContext getOrCreateActionContextAndResetToArguments(Clas /** * Creating Branch Record * - * @param method the method - * @param arguments the arguments - * @param businessAction the business action - * @param actionContext the action context + * @param method the method + * @param arguments the arguments + * @param businessActionParam the business action param + * @param actionContext the action context * @return the branchId */ - protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhaseBusinessAction businessAction, - BusinessActionContext actionContext) { + protected String doTxActionLogStore(Method method, Object[] arguments, TwoPhaseBusinessActionParam businessActionParam, + BusinessActionContext actionContext) { String actionName = actionContext.getActionName(); String xid = actionContext.getXid(); @@ -174,7 +179,7 @@ protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhase context.put(Constants.ACTION_START_TIME, System.currentTimeMillis()); //Init business context - initBusinessContext(context, method, businessAction); + initBusinessContext(context, method, businessActionParam.getBusinessActionContext()); //Init running environment context initFrameworkContext(context); @@ -191,15 +196,15 @@ protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhase //endregion //Init applicationData - Map applicationContext = Collections.singletonMap(Constants.TCC_ACTION_CONTEXT, context); + Map applicationContext = Collections.singletonMap(Constants.TX_ACTION_CONTEXT, context); String applicationContextStr = JSON.toJSONString(applicationContext); try { //registry branch record - Long branchId = DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid, + Long branchId = DefaultResourceManager.get().branchRegister(businessActionParam.getBranchType(), actionName, null, xid, applicationContextStr, null); return String.valueOf(branchId); } catch (Throwable t) { - String msg = String.format("TCC branch Register error, xid: %s", xid); + String msg = String.format("%s branch Register error, xid: %s", businessActionParam.getBranchType(), xid); LOGGER.error(msg, t); throw new FrameworkException(t, msg); } @@ -221,22 +226,19 @@ protected void initFrameworkContext(Map context) { /** * Init business context * - * @param context the context - * @param method the method - * @param businessAction the business action + * @param context the context + * @param method the method + * @param businessActionContext the business action map */ protected void initBusinessContext(Map context, Method method, - TwoPhaseBusinessAction businessAction) { + Map businessActionContext) { if (method != null) { //the phase one method name context.put(Constants.PREPARE_METHOD, method.getName()); } - if (businessAction != null) { + if (businessActionContext != null && businessActionContext.size() > 0) { //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()); - context.put(Constants.USE_TCC_FENCE, businessAction.useTCCFence()); + context.putAll(businessActionContext); } } @@ -247,7 +249,7 @@ protected void initBusinessContext(Map context, Method method, * @param arguments the arguments * @return the context */ - protected Map fetchActionRequestContext(Method method, Object[] arguments) { + public Map fetchActionRequestContext(Method method, Object[] arguments) { Map context = new HashMap<>(8); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); @@ -255,7 +257,7 @@ protected Map fetchActionRequestContext(Method method, Object[] for (int j = 0; j < parameterAnnotations[i].length; j++) { if (parameterAnnotations[i][j] instanceof BusinessActionContextParameter) { // get annotation - BusinessActionContextParameter annotation = (BusinessActionContextParameter)parameterAnnotations[i][j]; + BusinessActionContextParameter annotation = (BusinessActionContextParameter) parameterAnnotations[i][j]; if (arguments[i] == null) { throw new IllegalArgumentException("@BusinessActionContextParameter 's params can not null"); } @@ -273,4 +275,56 @@ protected Map fetchActionRequestContext(Method method, Object[] } return context; } + + /** + * get the method from interface + * + * @param invocation the invocation + * @return the action interface method + */ + public Method getActionInterfaceMethod(MethodInvocation invocation, RemotingDesc remotingDesc) { + Class interfaceType = null; + try { + if (remotingDesc == null) { + interfaceType = getProxyInterface(invocation.getThis()); + } else { + interfaceType = remotingDesc.getInterfaceClass(); + } + if (interfaceType == null && remotingDesc != null && remotingDesc.getInterfaceClassName() != null) { + interfaceType = Class.forName(remotingDesc.getInterfaceClassName(), true, + Thread.currentThread().getContextClassLoader()); + } + if (interfaceType == null) { + return invocation.getMethod(); + } + return interfaceType.getMethod(invocation.getMethod().getName(), + invocation.getMethod().getParameterTypes()); + } catch (NoSuchMethodException e) { + if (interfaceType != null && !"toString".equals(invocation.getMethod().getName())) { + LOGGER.warn("no such method '{}' from interface {}", invocation.getMethod().getName(), interfaceType.getName()); + } + return invocation.getMethod(); + } catch (Exception e) { + LOGGER.warn("get Method from interface failed", e); + return invocation.getMethod(); + } + } + + /** + * get the interface of proxy + * + * @param proxyBean the proxy bean + * @return proxy interface + * @throws Exception the exception + */ + @Nullable + protected Class getProxyInterface(Object proxyBean) throws Exception { + if (DubboUtil.isDubboProxyName(proxyBean.getClass().getName())) { + //dubbo javaassist proxy + return DubboUtil.getAssistInterface(proxyBean); + } else { + //jdk/cglib proxy + return SpringProxyUtils.getTargetInterface(proxyBean); + } + } } diff --git a/spring/src/main/java/io/seata/spring/interceptor/TwoPhaseBusinessActionParam.java b/spring/src/main/java/io/seata/spring/interceptor/TwoPhaseBusinessActionParam.java new file mode 100644 index 00000000000..c9c4cfae035 --- /dev/null +++ b/spring/src/main/java/io/seata/spring/interceptor/TwoPhaseBusinessActionParam.java @@ -0,0 +1,78 @@ +/* + * 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.spring.interceptor; + +import io.seata.core.model.BranchType; + +import java.util.Map; + +/** + * The two phase business action parameters. + * + * @author ruishansun + */ +public class TwoPhaseBusinessActionParam { + + private String actionName; + + private Boolean isDelayReport; + + private Boolean useCommonFence; + + private Map businessActionContext; + + private BranchType branchType; + + public String getActionName() { + return actionName; + } + + public void setActionName(String actionName) { + this.actionName = actionName; + } + + public Boolean getDelayReport() { + return isDelayReport; + } + + public void setDelayReport(Boolean delayReport) { + isDelayReport = delayReport; + } + + public Boolean getUseCommonFence() { + return useCommonFence; + } + + public void setUseCommonFence(Boolean useCommonFence) { + this.useCommonFence = useCommonFence; + } + + public Map getBusinessActionContext() { + return businessActionContext; + } + + public void setBusinessActionContext(Map businessActionContext) { + this.businessActionContext = businessActionContext; + } + + public BranchType getBranchType() { + return branchType; + } + + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } +} diff --git a/spring/src/main/java/io/seata/spring/tcc/TccAnnotationProcessor.java b/spring/src/main/java/io/seata/spring/interceptor/TxAnnotationProcessor.java similarity index 70% rename from spring/src/main/java/io/seata/spring/tcc/TccAnnotationProcessor.java rename to spring/src/main/java/io/seata/spring/interceptor/TxAnnotationProcessor.java index c2674cc5dfc..9fe395c6d83 100644 --- a/spring/src/main/java/io/seata/spring/tcc/TccAnnotationProcessor.java +++ b/spring/src/main/java/io/seata/spring/interceptor/TxAnnotationProcessor.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.tcc; +package io.seata.spring.interceptor; -import io.seata.rm.tcc.api.TwoPhaseBusinessAction; -import io.seata.rm.tcc.remoting.RemotingDesc; -import io.seata.spring.util.TCCBeanParserUtils; +import io.seata.spring.autoproxy.DefaultTransactionAutoProxy; +import io.seata.spring.autoproxy.IsTransactionProxyResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -26,7 +25,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashSet; @@ -35,12 +33,12 @@ import java.util.Set; /** - * An annotation adapter for TCC + * An annotation adapter for transaction annotation * * @author ppf */ -public class TccAnnotationProcessor implements BeanPostProcessor { - private static final Logger LOGGER = LoggerFactory.getLogger(TccAnnotationProcessor.class); +public class TxAnnotationProcessor implements BeanPostProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(TxAnnotationProcessor.class); private static final List> ANNOTATIONS = new ArrayList<>(4); private static final Set PROXIED_SET = new HashSet<>(); @@ -76,7 +74,7 @@ protected void process(Object bean, String beanName, Class return; } - addTccAdvise(bean, beanName, field, field.getType()); + addTxAdvise(bean, beanName, field, field.getType()); }, field -> !Modifier.isStatic(field.getModifiers()) && (field.isAnnotationPresent(annotation))); @@ -85,7 +83,7 @@ protected void process(Object bean, String beanName, Class } /** - * Add TCC interceptor for tcc proxy bean + * Add transaction interceptor for common transaction proxy bean * * @param bean the bean * @param beanName the bean name @@ -93,22 +91,18 @@ protected void process(Object bean, String beanName, Class * @param interfaceClass the interface class * @throws IllegalAccessException the illegal access exception */ - public void addTccAdvise(Object bean, String beanName, Field field, Class interfaceClass) throws IllegalAccessException { + public void addTxAdvise(Object bean, String beanName, Field field, Class interfaceClass) throws IllegalAccessException { Object fieldValue = field.get(bean); if (fieldValue == null) { return; } - for (Method method : field.getType().getMethods()) { - if (!Modifier.isStatic(method.getModifiers()) && (method.isAnnotationPresent(TwoPhaseBusinessAction.class))) { - RemotingDesc remotingDesc = new RemotingDesc(); - remotingDesc.setInterfaceClass(interfaceClass); - - TccActionInterceptor actionInterceptor = new TccActionInterceptor(remotingDesc); - Object proxyBean = TCCBeanParserUtils.createProxy(interfaceClass, fieldValue, actionInterceptor); - field.setAccessible(true); - field.set(bean, proxyBean); - LOGGER.info("Bean[" + bean.getClass().getName() + "] with name [" + field.getName() + "] would use proxy [" + actionInterceptor.getClass().getName() + "]"); - } + + IsTransactionProxyResult isProxyTargetBeanResult = DefaultTransactionAutoProxy.get().getIsProxyTargetBeanResult(beanName); + if (isProxyTargetBeanResult.isProxyTargetBean()) { + Object proxyBean = TxBeanParserUtils.createProxy(interfaceClass, fieldValue, isProxyTargetBeanResult.getMethodInterceptor()); + field.setAccessible(true); + field.set(bean, proxyBean); + LOGGER.info("Bean[" + bean.getClass().getName() + "] with name [" + field.getName() + "] would use proxy [" + isProxyTargetBeanResult.getMethodInterceptor().getClass().getName() + "]"); } } diff --git a/spring/src/main/java/io/seata/spring/util/TCCBeanParserUtils.java b/spring/src/main/java/io/seata/spring/interceptor/TxBeanParserUtils.java similarity index 58% rename from spring/src/main/java/io/seata/spring/util/TCCBeanParserUtils.java rename to spring/src/main/java/io/seata/spring/interceptor/TxBeanParserUtils.java index 5c7c606ffab..4554c648778 100644 --- a/spring/src/main/java/io/seata/spring/util/TCCBeanParserUtils.java +++ b/spring/src/main/java/io/seata/spring/interceptor/TxBeanParserUtils.java @@ -13,48 +13,47 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.util; +package io.seata.spring.interceptor; import io.seata.common.DefaultValues; -import io.seata.rm.tcc.api.TwoPhaseBusinessAction; -import io.seata.rm.tcc.config.TCCFenceConfig; -import io.seata.rm.tcc.remoting.Protocols; -import io.seata.rm.tcc.remoting.RemotingDesc; -import io.seata.rm.tcc.remoting.RemotingParser; -import io.seata.rm.tcc.remoting.parser.DefaultRemotingParser; -import io.seata.spring.tcc.TccActionInterceptor; +import io.seata.spring.autoproxy.DefaultTransactionAutoProxy; +import io.seata.spring.fence.config.CommonFenceConfig; +import io.seata.spring.remoting.Protocols; +import io.seata.spring.remoting.RemotingDesc; +import io.seata.spring.remoting.RemotingParser; +import io.seata.spring.remoting.parser.DefaultRemotingParser; +import io.seata.spring.util.SpringProxyUtils; +import org.aopalliance.intercept.MethodInterceptor; import org.springframework.aop.framework.ProxyFactory; import org.springframework.context.ApplicationContext; -import java.lang.reflect.Method; - /** - * parser TCC bean + * parser transaction bean * * @author zhangsen */ -public class TCCBeanParserUtils { +public class TxBeanParserUtils { - private TCCBeanParserUtils() { + private TxBeanParserUtils() { } /** - * is auto proxy TCC bean + * is auto proxy transaction bean * * @param bean the bean * @param beanName the bean name * @param applicationContext the application context * @return boolean boolean */ - public static boolean isTccAutoProxy(Object bean, String beanName, ApplicationContext applicationContext) { + public static boolean isTxAutoProxy(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); + //LocalService + return DefaultTransactionAutoProxy.get().isTransactionAutoProxy(beanName, remotingDesc); } else { // sofa:reference / dubbo:reference, factory bean return false; @@ -64,12 +63,12 @@ public static boolean isTccAutoProxy(Object bean, String beanName, ApplicationCo //check FactoryBean if (isRemotingFactoryBean(bean, beanName, applicationContext)) { remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(beanName); - return isTccProxyTargetBean(remotingDesc); + return DefaultTransactionAutoProxy.get().isTransactionAutoProxy(beanName, remotingDesc); } else { return false; } } else { - return isTccProxyTargetBean(remotingDesc); + return DefaultTransactionAutoProxy.get().isTransactionAutoProxy(beanName, remotingDesc); } } } @@ -102,66 +101,24 @@ protected static boolean isRemotingFactoryBean(Object bean, String beanName, } /** - * is TCC proxy-bean/target-bean: LocalTCC , the proxy bean of sofa:reference/dubbo:reference - * - * @param remotingDesc the remoting desc - * @return boolean boolean - */ - public static boolean isTccProxyTargetBean(RemotingDesc remotingDesc) { - if (remotingDesc == null) { - return false; - } - //check if it is TCC bean - boolean isTccClazz = false; - Class tccInterfaceClazz = remotingDesc.getInterfaceClass(); - Method[] methods = tccInterfaceClazz.getMethods(); - TwoPhaseBusinessAction twoPhaseBusinessAction; - for (Method method : methods) { - twoPhaseBusinessAction = method.getAnnotation(TwoPhaseBusinessAction.class); - if (twoPhaseBusinessAction != null) { - isTccClazz = true; - break; - } - } - if (!isTccClazz) { - return false; - } - short protocols = remotingDesc.getProtocol(); - //LocalTCC - if (Protocols.IN_JVM == protocols) { - //in jvm TCC bean , AOP - return true; - } - // sofa:reference / dubbo:reference, AOP - return remotingDesc.isReference(); - } - - /** - * init tcc fence clean task if enable useTccFence + * init common fence clean task if enable useCommonFence * * @param remotingDesc the remoting desc * @param applicationContext applicationContext */ - public static void initTccFenceCleanTask(RemotingDesc remotingDesc, ApplicationContext applicationContext) { + public static void initCommonFenceCleanTask(RemotingDesc remotingDesc, ApplicationContext applicationContext, boolean useCommonFence) { if (remotingDesc == null) { return; } - if (applicationContext != null && applicationContext.containsBean(DefaultValues.TCC_FENCE_BEAN_NAME)) { - TCCFenceConfig tccFenceConfig = (TCCFenceConfig) applicationContext.getBean(DefaultValues.TCC_FENCE_BEAN_NAME); - if (tccFenceConfig == null || tccFenceConfig.getInitialized().get()) { + if (applicationContext != null && applicationContext.containsBean(DefaultValues.COMMON_FENCE_BEAN_NAME)) { + CommonFenceConfig commonFenceConfig = (CommonFenceConfig) applicationContext.getBean(DefaultValues.COMMON_FENCE_BEAN_NAME); + if (commonFenceConfig == null || commonFenceConfig.getInitialized().get()) { return; } - Class tccInterfaceClazz = remotingDesc.getInterfaceClass(); - Method[] methods = tccInterfaceClazz.getMethods(); - for (Method method : methods) { - TwoPhaseBusinessAction twoPhaseBusinessAction = method.getAnnotation(TwoPhaseBusinessAction.class); - if (twoPhaseBusinessAction != null && twoPhaseBusinessAction.useTCCFence()) { - if (tccFenceConfig.getInitialized().compareAndSet(false, true)) { - // init tcc fence clean task if enable useTccFence - tccFenceConfig.initCleanTask(); - break; - } - } + + if (useCommonFence && commonFenceConfig.getInitialized().compareAndSet(false, true)) { + // init common fence clean task if enable useCommonFence + commonFenceConfig.initCleanTask(); } } } @@ -182,7 +139,7 @@ protected static boolean parserRemotingServiceInfo(Object bean, String beanName) } /** - * get the remoting description of TCC bean + * get the remoting description of Tx bean * * @param beanName the bean name * @return remoting desc @@ -192,14 +149,14 @@ public static RemotingDesc getRemotingDesc(String beanName) { } /** - * Create a proxy bean for tcc service + * Create a proxy bean for transaction service * * @param interfaceClass * @param fieldValue * @param actionInterceptor * @return */ - public static T createProxy(Class interfaceClass, Object fieldValue, TccActionInterceptor actionInterceptor) { + public static T createProxy(Class interfaceClass, Object fieldValue, MethodInterceptor actionInterceptor) { ProxyFactory factory = new ProxyFactory(); factory.setTarget(fieldValue); factory.setInterfaces(interfaceClass); diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/Protocols.java b/spring/src/main/java/io/seata/spring/remoting/Protocols.java similarity index 96% rename from tcc/src/main/java/io/seata/rm/tcc/remoting/Protocols.java rename to spring/src/main/java/io/seata/spring/remoting/Protocols.java index 3d38f71e82a..a1048f494ff 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/Protocols.java +++ b/spring/src/main/java/io/seata/spring/remoting/Protocols.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.remoting; +package io.seata.spring.remoting; /** * remoting protocols enum diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/RemotingDesc.java b/spring/src/main/java/io/seata/spring/remoting/RemotingDesc.java similarity index 89% rename from tcc/src/main/java/io/seata/rm/tcc/remoting/RemotingDesc.java rename to spring/src/main/java/io/seata/spring/remoting/RemotingDesc.java index df91bad93b0..672ab4d55b0 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/RemotingDesc.java +++ b/spring/src/main/java/io/seata/spring/remoting/RemotingDesc.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.remoting; +package io.seata.spring.remoting; /** * remoting bean info @@ -26,6 +26,11 @@ public class RemotingDesc { * is referenc bean ? */ private boolean isReference = false; + + /** + * is service bean ? + */ + private boolean isService = false; /** * rpc target bean, the service bean has this property @@ -182,4 +187,21 @@ public boolean isReference() { public void setReference(boolean reference) { isReference = reference; } + + /** + * Is service boolean. + * + * @return the boolean + */ + public boolean isService() { + return isService; + } + + /** + * Sets service. + * @param service the service + */ + public void setService(boolean service) { + isService = service; + } } diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/RemotingParser.java b/spring/src/main/java/io/seata/spring/remoting/RemotingParser.java similarity index 98% rename from tcc/src/main/java/io/seata/rm/tcc/remoting/RemotingParser.java rename to spring/src/main/java/io/seata/spring/remoting/RemotingParser.java index 2ff32ca4980..bbed5d2e101 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/RemotingParser.java +++ b/spring/src/main/java/io/seata/spring/remoting/RemotingParser.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.remoting; +package io.seata.spring.remoting; import io.seata.common.exception.FrameworkException; diff --git a/tcc/src/main/java/io/seata/rm/tcc/TwoPhaseResult.java b/spring/src/main/java/io/seata/spring/remoting/TwoPhaseResult.java similarity index 98% rename from tcc/src/main/java/io/seata/rm/tcc/TwoPhaseResult.java rename to spring/src/main/java/io/seata/spring/remoting/TwoPhaseResult.java index 54b4c3139ee..7dbab5c81e6 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/TwoPhaseResult.java +++ b/spring/src/main/java/io/seata/spring/remoting/TwoPhaseResult.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc; +package io.seata.spring.remoting; import io.seata.common.util.StringUtils; diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/AbstractedRemotingParser.java b/spring/src/main/java/io/seata/spring/remoting/parser/AbstractedRemotingParser.java similarity index 91% rename from tcc/src/main/java/io/seata/rm/tcc/remoting/parser/AbstractedRemotingParser.java rename to spring/src/main/java/io/seata/spring/remoting/parser/AbstractedRemotingParser.java index 153326688ea..2bc018772ee 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/AbstractedRemotingParser.java +++ b/spring/src/main/java/io/seata/spring/remoting/parser/AbstractedRemotingParser.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.remoting.parser; +package io.seata.spring.remoting.parser; import io.seata.common.exception.FrameworkException; -import io.seata.rm.tcc.remoting.RemotingParser; +import io.seata.spring.remoting.RemotingParser; /** * The type Abstracted remoting parser. diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/DefaultRemotingParser.java b/spring/src/main/java/io/seata/spring/remoting/parser/DefaultRemotingParser.java similarity index 70% rename from tcc/src/main/java/io/seata/rm/tcc/remoting/parser/DefaultRemotingParser.java rename to spring/src/main/java/io/seata/spring/remoting/parser/DefaultRemotingParser.java index aebd385bb02..d140295186c 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/DefaultRemotingParser.java +++ b/spring/src/main/java/io/seata/spring/remoting/parser/DefaultRemotingParser.java @@ -13,7 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.remoting.parser; +package io.seata.spring.remoting.parser; + +import io.seata.common.exception.FrameworkException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.tcc.api.BusinessActionContext; +import io.seata.rm.tcc.api.BusinessActionContextParameter; +import io.seata.spring.interceptor.ActionContextUtil; +import io.seata.spring.remoting.RemotingDesc; +import io.seata.spring.remoting.RemotingParser; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -22,18 +31,6 @@ 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.rm.DefaultResourceManager; -import io.seata.rm.tcc.TCCResource; -import io.seata.rm.tcc.api.BusinessActionContext; -import io.seata.rm.tcc.api.BusinessActionContextParameter; -import io.seata.rm.tcc.api.TwoPhaseBusinessAction; -import io.seata.rm.tcc.interceptor.ActionContextUtil; -import io.seata.rm.tcc.remoting.RemotingDesc; -import io.seata.rm.tcc.remoting.RemotingParser; - /** * parsing remoting bean * @@ -169,42 +166,7 @@ public RemotingDesc parserRemotingServiceInfo(Object bean, String beanName, Remo 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(interfaceClass.getMethod(twoPhaseBusinessAction.commitMethod(), - twoPhaseBusinessAction.commitArgsClasses())); - tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod()); - tccResource.setRollbackMethod(interfaceClass.getMethod(twoPhaseBusinessAction.rollbackMethod(), - twoPhaseBusinessAction.rollbackArgsClasses())); - // set argsClasses - tccResource.setCommitArgsClasses(twoPhaseBusinessAction.commitArgsClasses()); - tccResource.setRollbackArgsClasses(twoPhaseBusinessAction.rollbackArgsClasses()); - // set phase two method's keys - tccResource.setPhaseTwoCommitKeys(this.getTwoPhaseArgs(tccResource.getCommitMethod(), - twoPhaseBusinessAction.commitArgsClasses())); - tccResource.setPhaseTwoRollbackKeys(this.getTwoPhaseArgs(tccResource.getRollbackMethod(), - twoPhaseBusinessAction.rollbackArgsClasses())); - //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); @@ -212,7 +174,7 @@ public RemotingDesc parserRemotingServiceInfo(Object bean, String beanName, Remo return remotingBeanDesc; } - protected String[] getTwoPhaseArgs(Method method, Class[] argsClasses) { + public String[] getTwoPhaseArgs(Method method, Class[] argsClasses) { Annotation[][] parameterAnnotations = method.getParameterAnnotations(); String[] keys = new String[parameterAnnotations.length]; /* diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/DubboRemotingParser.java b/spring/src/main/java/io/seata/spring/remoting/parser/DubboRemotingParser.java similarity index 91% rename from tcc/src/main/java/io/seata/rm/tcc/remoting/parser/DubboRemotingParser.java rename to spring/src/main/java/io/seata/spring/remoting/parser/DubboRemotingParser.java index c47f0342c59..ecbd3bc5bae 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/DubboRemotingParser.java +++ b/spring/src/main/java/io/seata/spring/remoting/parser/DubboRemotingParser.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.remoting.parser; +package io.seata.spring.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; +import io.seata.spring.remoting.Protocols; +import io.seata.spring.remoting.RemotingDesc; /** * dubbo remoting bean parsing @@ -61,6 +61,8 @@ public RemotingDesc getServiceDesc(Object bean, String beanName) throws Framewor Object targetBean = ReflectionUtil.getFieldValue(bean, "ref"); serviceBeanDesc.setTargetBean(targetBean); } + serviceBeanDesc.setReference(this.isReference(bean, beanName)); + serviceBeanDesc.setService(this.isService(bean, beanName)); return serviceBeanDesc; } catch (Throwable t) { throw new FrameworkException(t); diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/HSFRemotingParser.java b/spring/src/main/java/io/seata/spring/remoting/parser/HSFRemotingParser.java similarity index 91% rename from tcc/src/main/java/io/seata/rm/tcc/remoting/parser/HSFRemotingParser.java rename to spring/src/main/java/io/seata/spring/remoting/parser/HSFRemotingParser.java index 7dd4a8611f3..6915fb3607b 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/HSFRemotingParser.java +++ b/spring/src/main/java/io/seata/spring/remoting/parser/HSFRemotingParser.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.remoting.parser; +package io.seata.spring.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; +import io.seata.spring.remoting.Protocols; +import io.seata.spring.remoting.RemotingDesc; /** * HSF Remote Bean Parser @@ -85,6 +85,8 @@ public RemotingDesc getServiceDesc(Object bean, String beanName) throws Framewor serviceBeanDesc.setUniqueId(uniqueId); serviceBeanDesc.setGroup(group); serviceBeanDesc.setProtocol(Protocols.HSF); + serviceBeanDesc.setReference(this.isReference(bean, beanName)); + serviceBeanDesc.setService(this.isService(bean, beanName)); return serviceBeanDesc; } else if (isService(bean, beanName)) { Object consumerBean = ReflectionUtil.getFieldValue(bean, "providerBean"); @@ -103,6 +105,8 @@ public RemotingDesc getServiceDesc(Object bean, String beanName) throws Framewor Object targetBean = ReflectionUtil.getFieldValue(metadata, "target"); serviceBeanDesc.setTargetBean(targetBean); serviceBeanDesc.setProtocol(Protocols.HSF); + serviceBeanDesc.setReference(this.isReference(bean, beanName)); + serviceBeanDesc.setService(this.isService(bean, beanName)); return serviceBeanDesc; } } catch (Throwable t) { diff --git a/spring/src/main/java/io/seata/spring/remoting/parser/LocalServiceRemotingParser.java b/spring/src/main/java/io/seata/spring/remoting/parser/LocalServiceRemotingParser.java new file mode 100644 index 00000000000..23970b28fcd --- /dev/null +++ b/spring/src/main/java/io/seata/spring/remoting/parser/LocalServiceRemotingParser.java @@ -0,0 +1,86 @@ +/* + * 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.spring.remoting.parser; + +import io.seata.common.exception.FrameworkException; +import io.seata.common.util.ReflectionUtil; +import io.seata.spring.annotation.LocalService; +import io.seata.spring.remoting.Protocols; +import io.seata.spring.remoting.RemotingDesc; + +import java.util.Set; + +/** + * Local Service Remote Bean Parser + * + * @author ruishansun + */ +public class LocalServiceRemotingParser extends AbstractedRemotingParser { + + @Override + public boolean isReference(Object bean, String beanName) { + return isLocalService(bean); + } + + @Override + public boolean isService(Object bean, String beanName) { + return isLocalService(bean); + } + + @Override + public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException { + if (!this.isRemoting(bean, beanName)) { + return null; + } + RemotingDesc remotingDesc = new RemotingDesc(); + remotingDesc.setReference(this.isReference(bean, beanName)); + remotingDesc.setService(this.isService(bean, beanName)); + remotingDesc.setProtocol(Protocols.IN_JVM); + Class classType = bean.getClass(); + Set> interfaceClasses = ReflectionUtil.getInterfaces(classType); + for (Class interClass : interfaceClasses) { + if (interClass.isAnnotationPresent(LocalService.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; + } + + /** + * Determine whether there is an annotation {@link LocalService} + * + * @param bean the bean + * @return boolean + */ + private boolean isLocalService(Object bean) { + Class classType = bean.getClass(); + Set> interfaceClasses = ReflectionUtil.getInterfaces(classType); + for (Class interClass : interfaceClasses) { + if (interClass.isAnnotationPresent(LocalService.class)) { + return true; + } + } + return false; + } +} diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/SofaRpcRemotingParser.java b/spring/src/main/java/io/seata/spring/remoting/parser/SofaRpcRemotingParser.java similarity index 90% rename from tcc/src/main/java/io/seata/rm/tcc/remoting/parser/SofaRpcRemotingParser.java rename to spring/src/main/java/io/seata/spring/remoting/parser/SofaRpcRemotingParser.java index 6673be5fe61..95c0c10b949 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/SofaRpcRemotingParser.java +++ b/spring/src/main/java/io/seata/spring/remoting/parser/SofaRpcRemotingParser.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.remoting.parser; +package io.seata.spring.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; +import io.seata.spring.remoting.Protocols; +import io.seata.spring.remoting.RemotingDesc; /** * sofa-rpc remoting bean parsing @@ -60,6 +60,8 @@ public RemotingDesc getServiceDesc(Object bean, String beanName) throws Framewor Object targetBean = ReflectionUtil.getFieldValue(bean, "ref"); serviceBeanDesc.setTargetBean(targetBean); } + serviceBeanDesc.setReference(this.isReference(bean, beanName)); + serviceBeanDesc.setService(this.isService(bean, beanName)); return serviceBeanDesc; } catch (Throwable t) { throw new FrameworkException(t); diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/DubboUtil.java b/spring/src/main/java/io/seata/spring/util/DubboUtil.java similarity index 98% rename from tcc/src/main/java/io/seata/rm/tcc/remoting/parser/DubboUtil.java rename to spring/src/main/java/io/seata/spring/util/DubboUtil.java index c33651ca09b..e2482b8406e 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/DubboUtil.java +++ b/spring/src/main/java/io/seata/spring/util/DubboUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.rm.tcc.remoting.parser; +package io.seata.spring.util; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; diff --git a/spring/src/main/java/io/seata/spring/util/SpringProxyUtils.java b/spring/src/main/java/io/seata/spring/util/SpringProxyUtils.java index 567903b75ac..cbc94662a26 100644 --- a/spring/src/main/java/io/seata/spring/util/SpringProxyUtils.java +++ b/spring/src/main/java/io/seata/spring/util/SpringProxyUtils.java @@ -22,7 +22,6 @@ import java.util.Set; import io.seata.common.util.CollectionUtils; -import io.seata.rm.tcc.remoting.parser.DubboUtil; import org.springframework.aop.TargetSource; import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.AdvisedSupport; diff --git a/spring/src/main/resources/META-INF/services/io.seata.spring.autoproxy.TransactionAutoProxy b/spring/src/main/resources/META-INF/services/io.seata.spring.autoproxy.TransactionAutoProxy new file mode 100644 index 00000000000..fa72b433d45 --- /dev/null +++ b/spring/src/main/resources/META-INF/services/io.seata.spring.autoproxy.TransactionAutoProxy @@ -0,0 +1,2 @@ +io.seata.rm.tcc.autoproxy.TccTransactionAutoProxy +io.seata.saga.annotation.autoproxy.SagaTransactionAutoProxy \ No newline at end of file diff --git a/spring/src/main/resources/META-INF/services/io.seata.spring.remoting.RemotingParser b/spring/src/main/resources/META-INF/services/io.seata.spring.remoting.RemotingParser new file mode 100644 index 00000000000..491f15d2119 --- /dev/null +++ b/spring/src/main/resources/META-INF/services/io.seata.spring.remoting.RemotingParser @@ -0,0 +1,5 @@ +io.seata.spring.remoting.parser.DubboRemotingParser +io.seata.rm.tcc.remoting.parser.LocalTCCRemotingParser +io.seata.spring.remoting.parser.SofaRpcRemotingParser +io.seata.spring.remoting.parser.HSFRemotingParser +io.seata.spring.remoting.parser.LocalServiceRemotingParser \ No newline at end of file diff --git a/spring/src/test/java/io/seata/spring/remoting/LocalServiceAction.java b/spring/src/test/java/io/seata/spring/remoting/LocalServiceAction.java new file mode 100644 index 00000000000..e58afa45a47 --- /dev/null +++ b/spring/src/test/java/io/seata/spring/remoting/LocalServiceAction.java @@ -0,0 +1,29 @@ +/* + * 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.spring.remoting; + +import io.seata.spring.annotation.LocalService; + +/** + * The interface local service action. + * + * @author ruishansun + */ +@LocalService +public interface LocalServiceAction { + + +} diff --git a/spring/src/test/java/io/seata/spring/remoting/LocalServiceActionImpl.java b/spring/src/test/java/io/seata/spring/remoting/LocalServiceActionImpl.java new file mode 100644 index 00000000000..75a8cd98b61 --- /dev/null +++ b/spring/src/test/java/io/seata/spring/remoting/LocalServiceActionImpl.java @@ -0,0 +1,24 @@ +/* + * 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.spring.remoting; + +/** + * The interface local service action impl. + * + * @author ruishansun + */ +public class LocalServiceActionImpl implements LocalServiceAction { +} diff --git a/spring/src/test/java/io/seata/spring/remoting/NormalServiceActionImpl.java b/spring/src/test/java/io/seata/spring/remoting/NormalServiceActionImpl.java new file mode 100644 index 00000000000..a497c141513 --- /dev/null +++ b/spring/src/test/java/io/seata/spring/remoting/NormalServiceActionImpl.java @@ -0,0 +1,4 @@ +package io.seata.spring.remoting; + +public class NormalServiceActionImpl { +} diff --git a/spring/src/test/java/io/seata/spring/remoting/parser/LocalServiceRemotingParserTest.java b/spring/src/test/java/io/seata/spring/remoting/parser/LocalServiceRemotingParserTest.java new file mode 100644 index 00000000000..3dabdb2be87 --- /dev/null +++ b/spring/src/test/java/io/seata/spring/remoting/parser/LocalServiceRemotingParserTest.java @@ -0,0 +1,71 @@ +/* + * 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.spring.remoting.parser; + +import io.seata.spring.remoting.LocalServiceAction; +import io.seata.spring.remoting.LocalServiceActionImpl; +import io.seata.spring.remoting.RemotingDesc; +import io.seata.spring.remoting.Protocols; +import io.seata.spring.remoting.NormalServiceActionImpl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Local service remoting parser test. + * + * @author ruishansun + */ +public class LocalServiceRemotingParserTest { + + /** + * The Local service remoting parser. + */ + LocalServiceRemotingParser localServiceRemotingParser = new LocalServiceRemotingParser(); + + /** + * Test service desc. + */ + @Test + public void testServiceDesc() { + LocalServiceAction localServiceAction = new LocalServiceActionImpl(); + + RemotingDesc remotingDesc = localServiceRemotingParser.getServiceDesc(localServiceAction, "c"); + Assertions.assertNotNull(remotingDesc); + + Assertions.assertEquals("io.seata.spring.remoting.LocalServiceAction", remotingDesc.getInterfaceClassName()); + Assertions.assertEquals(remotingDesc.getInterfaceClass(), LocalServiceAction.class); + Assertions.assertEquals(remotingDesc.getTargetBean(), localServiceAction); + } + + @Test + public void testProtocol() { + Assertions.assertEquals(Protocols.IN_JVM, localServiceRemotingParser.getProtocol()); + } + + @Test + public void testNoRemotingDesc() { + NormalServiceActionImpl normalServiceAction = new NormalServiceActionImpl(); + RemotingDesc remotingDesc = localServiceRemotingParser.getServiceDesc(normalServiceAction, "normalServiceAction"); + Assertions.assertNull(remotingDesc); + } + + @Test + public void testNotIsService() { + NormalServiceActionImpl normalServiceAction = new NormalServiceActionImpl(); + Assertions.assertFalse(localServiceRemotingParser.isService(normalServiceAction, "normalServiceAction")); + } + +} diff --git a/tcc/pom.xml b/tcc/pom.xml index 2749e9d1273..de7440d96aa 100644 --- a/tcc/pom.xml +++ b/tcc/pom.xml @@ -41,12 +41,9 @@ ${project.version} - org.springframework - spring-jdbc - - - com.alibaba - fastjson + ${project.groupId} + seata-spring + ${project.version} diff --git a/tcc/src/main/java/io/seata/rm/tcc/TCCResourceManager.java b/tcc/src/main/java/io/seata/rm/tcc/TCCResourceManager.java index f431861e7ce..75dea74b110 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/TCCResourceManager.java +++ b/tcc/src/main/java/io/seata/rm/tcc/TCCResourceManager.java @@ -15,23 +15,23 @@ */ package io.seata.rm.tcc; -import java.lang.reflect.Method; -import java.lang.reflect.UndeclaredThrowableException; -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.exception.SkipCallbackWrapperException; -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; +import io.seata.rm.tcc.api.BusinessActionContextUtil; +import io.seata.spring.fence.CommonFenceHandler; +import io.seata.spring.remoting.TwoPhaseResult; + +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * TCC resource manager @@ -95,15 +95,16 @@ public BranchStatus branchCommit(BranchType branchType, String xid, long branchI } try { //BusinessActionContext - BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId, + BusinessActionContext businessActionContext = BusinessActionContextUtil.getBusinessActionContext(xid, branchId, resourceId, applicationData); + Object[] args = this.getTwoPhaseCommitArgs(tccResource, businessActionContext); Object ret; boolean result; // add idempotent and anti hanging - if (Boolean.TRUE.equals(businessActionContext.getActionContext(Constants.USE_TCC_FENCE))) { + if (Boolean.TRUE.equals(businessActionContext.getActionContext(Constants.USE_COMMON_FENCE))) { try { - result = TCCFenceHandler.commitFence(commitMethod, targetTCCBean, xid, branchId, args); + result = CommonFenceHandler.commitFence(commitMethod, targetTCCBean, xid, branchId, args); } catch (SkipCallbackWrapperException | UndeclaredThrowableException e) { throw e.getCause(); } @@ -153,15 +154,15 @@ public BranchStatus branchRollback(BranchType branchType, String xid, long branc } try { //BusinessActionContext - BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId, + BusinessActionContext businessActionContext = BusinessActionContextUtil.getBusinessActionContext(xid, branchId, resourceId, applicationData); Object[] args = this.getTwoPhaseRollbackArgs(tccResource, businessActionContext); Object ret; boolean result; // add idempotent and anti hanging - if (Boolean.TRUE.equals(businessActionContext.getActionContext(Constants.USE_TCC_FENCE))) { + if (Boolean.TRUE.equals(businessActionContext.getActionContext(Constants.USE_COMMON_FENCE))) { try { - result = TCCFenceHandler.rollbackFence(rollbackMethod, targetTCCBean, xid, branchId, + result = CommonFenceHandler.rollbackFence(rollbackMethod, targetTCCBean, xid, branchId, args, tccResource.getActionName()); } catch (SkipCallbackWrapperException | UndeclaredThrowableException e) { throw e.getCause(); @@ -187,33 +188,6 @@ public BranchStatus branchRollback(BranchType branchType, String xid, long branc } } - /** - * 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) { - Map actionContextMap = null; - if (StringUtils.isNotBlank(applicationData)) { - Map tccContext = JSON.parseObject(applicationData, Map.class); - actionContextMap = (Map)tccContext.get(Constants.TCC_ACTION_CONTEXT); - } - if (actionContextMap == null) { - actionContextMap = new HashMap<>(2); - } - - //instance the action context - BusinessActionContext businessActionContext = new BusinessActionContext( - xid, String.valueOf(branchId), actionContextMap); - businessActionContext.setActionName(resourceId); - return businessActionContext; - } - /** * get phase two commit method's args * @param tccResource tccResource @@ -223,7 +197,7 @@ protected BusinessActionContext getBusinessActionContext(String xid, long branch private Object[] getTwoPhaseCommitArgs(TCCResource tccResource, BusinessActionContext businessActionContext) { String[] keys = tccResource.getPhaseTwoCommitKeys(); Class[] argsCommitClasses = tccResource.getCommitArgsClasses(); - return this.getTwoPhaseMethodParams(keys, argsCommitClasses, businessActionContext); + return BusinessActionContextUtil.getTwoPhaseMethodParams(keys, argsCommitClasses, businessActionContext); } /** @@ -235,19 +209,7 @@ private Object[] getTwoPhaseCommitArgs(TCCResource tccResource, BusinessActionCo private Object[] getTwoPhaseRollbackArgs(TCCResource tccResource, BusinessActionContext businessActionContext) { String[] keys = tccResource.getPhaseTwoRollbackKeys(); Class[] argsRollbackClasses = tccResource.getRollbackArgsClasses(); - return this.getTwoPhaseMethodParams(keys, argsRollbackClasses, businessActionContext); - } - - private Object[] getTwoPhaseMethodParams(String[] keys, Class[] argsClasses, BusinessActionContext businessActionContext) { - Object[] args = new Object[argsClasses.length]; - for (int i = 0; i < argsClasses.length; i++) { - if (argsClasses[i].equals(BusinessActionContext.class)) { - args[i] = businessActionContext; - } else { - args[i] = businessActionContext.getActionContext(keys[i], argsClasses[i]); - } - } - return args; + return BusinessActionContextUtil.getTwoPhaseMethodParams(keys, argsRollbackClasses, businessActionContext); } @Override diff --git a/tcc/src/main/java/io/seata/rm/tcc/api/TwoPhaseBusinessAction.java b/tcc/src/main/java/io/seata/rm/tcc/api/TwoPhaseBusinessAction.java index 0bc714b7b6e..28319d1baff 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/api/TwoPhaseBusinessAction.java +++ b/tcc/src/main/java/io/seata/rm/tcc/api/TwoPhaseBusinessAction.java @@ -15,6 +15,8 @@ */ package io.seata.rm.tcc.api; +import io.seata.rm.tcc.interceptor.TccActionInterceptor; + import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; @@ -29,7 +31,7 @@ * @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 + * @see TccActionInterceptor // the interceptor of TCC mode * @see BusinessActionContext * @see BusinessActionContextUtil * @see BusinessActionContextParameter diff --git a/tcc/src/main/java/io/seata/rm/tcc/autoproxy/TccTransactionAutoProxy.java b/tcc/src/main/java/io/seata/rm/tcc/autoproxy/TccTransactionAutoProxy.java new file mode 100644 index 00000000000..303a51197b8 --- /dev/null +++ b/tcc/src/main/java/io/seata/rm/tcc/autoproxy/TccTransactionAutoProxy.java @@ -0,0 +1,129 @@ +/* + * 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.autoproxy; + +import io.seata.common.exception.FrameworkException; +import io.seata.rm.DefaultResourceManager; +import io.seata.rm.tcc.TCCResource; +import io.seata.rm.tcc.api.TwoPhaseBusinessAction; +import io.seata.rm.tcc.interceptor.TccActionInterceptor; +import io.seata.spring.autoproxy.IsTransactionProxyResult; +import io.seata.spring.autoproxy.TransactionAutoProxy; +import io.seata.spring.remoting.Protocols; +import io.seata.spring.remoting.RemotingDesc; +import io.seata.spring.remoting.parser.DefaultRemotingParser; + +import java.lang.reflect.Method; + +/** + * the tcc implements of TransactionAutoProxy + * + * @author ruishansun + */ +public class TccTransactionAutoProxy implements TransactionAutoProxy { + + /** + * is TCC proxy-bean/target-bean: LocalTCC , the proxy bean of sofa:reference/dubbo:reference + * + * @param remotingDesc the remoting desc + * @return boolean + */ + @Override + public IsTransactionProxyResult isTransactionProxyTargetBean(RemotingDesc remotingDesc) { + if (remotingDesc == null) { + return new IsTransactionProxyResult(); + } + //check if it is TCC bean + boolean isTccClazz = false; + boolean userFence = false; + Class tccInterfaceClazz = remotingDesc.getInterfaceClass(); + Method[] methods = tccInterfaceClazz.getMethods(); + TwoPhaseBusinessAction twoPhaseBusinessAction; + for (Method method : methods) { + twoPhaseBusinessAction = method.getAnnotation(TwoPhaseBusinessAction.class); + if (twoPhaseBusinessAction != null) { + isTccClazz = true; + if (twoPhaseBusinessAction.useTCCFence()) { + userFence = true; + } + break; + } + } + if (!isTccClazz) { + return new IsTransactionProxyResult(); + } + short protocols = remotingDesc.getProtocol(); + //LocalTCC + if (Protocols.IN_JVM == protocols) { + this.registryResource(remotingDesc); + //in jvm TCC bean , AOP + IsTransactionProxyResult result = new IsTransactionProxyResult(); + result.setProxyTargetBean(true); + result.setUseCommonFence(userFence); + result.setMethodInterceptor(new TccActionInterceptor(remotingDesc)); + return result; + } + // sofa:reference / dubbo:reference, AOP + if (remotingDesc.isReference()) { + this.registryResource(remotingDesc); + IsTransactionProxyResult result = new IsTransactionProxyResult(); + result.setProxyTargetBean(true); + result.setUseCommonFence(userFence); + result.setMethodInterceptor(new TccActionInterceptor(remotingDesc)); + return result; + } else { + return new IsTransactionProxyResult(); + } + } + + private void registryResource(RemotingDesc remotingDesc) { + if (remotingDesc.isService()) { + try { + Class interfaceClass = remotingDesc.getInterfaceClass(); + Method[] methods = interfaceClass.getMethods(); + //service bean, registry resource + Object targetBean = remotingDesc.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(interfaceClass.getMethod(twoPhaseBusinessAction.commitMethod(), + twoPhaseBusinessAction.commitArgsClasses())); + tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod()); + tccResource.setRollbackMethod(interfaceClass.getMethod(twoPhaseBusinessAction.rollbackMethod(), + twoPhaseBusinessAction.rollbackArgsClasses())); + // set argsClasses + tccResource.setCommitArgsClasses(twoPhaseBusinessAction.commitArgsClasses()); + tccResource.setRollbackArgsClasses(twoPhaseBusinessAction.rollbackArgsClasses()); + // set phase two method's keys + tccResource.setPhaseTwoCommitKeys(DefaultRemotingParser.get().getTwoPhaseArgs(tccResource.getCommitMethod(), + twoPhaseBusinessAction.commitArgsClasses())); + tccResource.setPhaseTwoRollbackKeys(DefaultRemotingParser.get().getTwoPhaseArgs(tccResource.getRollbackMethod(), + twoPhaseBusinessAction.rollbackArgsClasses())); + //registry tcc resource + DefaultResourceManager.get().registerResource(tccResource); + } + } + } catch (Throwable t) { + throw new FrameworkException(t, "parser remoting service error"); + } + } + } +} diff --git a/spring/src/main/java/io/seata/spring/tcc/TccActionInterceptor.java b/tcc/src/main/java/io/seata/rm/tcc/interceptor/TccActionInterceptor.java similarity index 64% rename from spring/src/main/java/io/seata/spring/tcc/TccActionInterceptor.java rename to tcc/src/main/java/io/seata/rm/tcc/interceptor/TccActionInterceptor.java index 91fd540c7db..07a4dd20cb6 100644 --- a/spring/src/main/java/io/seata/spring/tcc/TccActionInterceptor.java +++ b/tcc/src/main/java/io/seata/rm/tcc/interceptor/TccActionInterceptor.java @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.tcc; - -import java.lang.reflect.Method; -import javax.annotation.Nullable; +package io.seata.rm.tcc.interceptor; +import io.seata.common.Constants; import io.seata.common.DefaultValues; import io.seata.config.ConfigurationChangeEvent; import io.seata.config.ConfigurationChangeListener; @@ -26,10 +24,9 @@ import io.seata.core.context.RootContext; import io.seata.core.model.BranchType; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; -import io.seata.rm.tcc.interceptor.ActionInterceptorHandler; -import io.seata.rm.tcc.remoting.RemotingDesc; -import io.seata.rm.tcc.remoting.parser.DubboUtil; -import io.seata.spring.util.SpringProxyUtils; +import io.seata.spring.interceptor.ActionInterceptorHandler; +import io.seata.spring.interceptor.TwoPhaseBusinessActionParam; +import io.seata.spring.remoting.RemotingDesc; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.slf4j.Logger; @@ -37,6 +34,10 @@ import org.slf4j.MDC; import org.springframework.core.Ordered; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + import static io.seata.common.DefaultValues.DEFAULT_DISABLE_GLOBAL_TRANSACTION; import static io.seata.core.constants.ConfigurationKeys.TCC_ACTION_INTERCEPTOR_ORDER; @@ -51,7 +52,10 @@ public class TccActionInterceptor implements MethodInterceptor, ConfigurationCha private static final Logger LOGGER = LoggerFactory.getLogger(TccActionInterceptor.class); private static final int ORDER_NUM = ConfigurationFactory.getInstance().getInt(TCC_ACTION_INTERCEPTOR_ORDER, DefaultValues.TCC_ACTION_INTERCEPTOR_ORDER); - + + /** + * TODO singleTone? + */ private ActionInterceptorHandler actionInterceptorHandler = new ActionInterceptorHandler(); private volatile boolean disable = ConfigurationFactory.getInstance().getBoolean( @@ -83,7 +87,7 @@ public Object invoke(final MethodInvocation invocation) throws Throwable { //not in transaction, or this interceptor is disabled return invocation.proceed(); } - Method method = getActionInterfaceMethod(invocation); + Method method = actionInterceptorHandler.getActionInterfaceMethod(invocation, this.remotingDesc); TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class); //try method if (businessAction != null) { @@ -96,8 +100,20 @@ public Object invoke(final MethodInvocation invocation) throws Throwable { RootContext.bindBranchType(BranchType.TCC); } try { + TwoPhaseBusinessActionParam businessActionParam = new TwoPhaseBusinessActionParam(); + businessActionParam.setActionName(businessAction.name()); + businessActionParam.setDelayReport(businessAction.isDelayReport()); + businessActionParam.setUseCommonFence(businessAction.useTCCFence()); + businessActionParam.setBranchType(BranchType.TCC); + Map businessActionContextMap = new HashMap<>(4); + //the phase two method name + businessActionContextMap.put(Constants.COMMIT_METHOD, businessAction.commitMethod()); + businessActionContextMap.put(Constants.ROLLBACK_METHOD, businessAction.rollbackMethod()); + businessActionContextMap.put(Constants.ACTION_NAME, businessAction.name()); + businessActionContextMap.put(Constants.USE_COMMON_FENCE, businessAction.useTCCFence()); + businessActionParam.setBusinessActionContext(businessActionContextMap); //Handler the TCC Aspect, and return the business result - return actionInterceptorHandler.proceed(method, invocation.getArguments(), xid, businessAction, + return actionInterceptorHandler.proceed(method, invocation.getArguments(), xid, businessActionParam, invocation::proceed); } finally { //if not TCC, unbind branchType @@ -113,58 +129,6 @@ public Object invoke(final MethodInvocation invocation) throws Throwable { return invocation.proceed(); } - /** - * get the method from interface - * - * @param invocation the invocation - * @return the action interface method - */ - protected Method getActionInterfaceMethod(MethodInvocation invocation) { - Class interfaceType = null; - try { - if (remotingDesc == null) { - interfaceType = getProxyInterface(invocation.getThis()); - } else { - interfaceType = remotingDesc.getInterfaceClass(); - } - if (interfaceType == null && remotingDesc != null && remotingDesc.getInterfaceClassName() != null) { - interfaceType = Class.forName(remotingDesc.getInterfaceClassName(), true, - Thread.currentThread().getContextClassLoader()); - } - if (interfaceType == null) { - return invocation.getMethod(); - } - return interfaceType.getMethod(invocation.getMethod().getName(), - invocation.getMethod().getParameterTypes()); - } catch (NoSuchMethodException e) { - if (interfaceType != null && !"toString".equals(invocation.getMethod().getName())) { - LOGGER.warn("no such method '{}' from interface {}", invocation.getMethod().getName(), interfaceType.getName()); - } - return invocation.getMethod(); - } catch (Exception e) { - LOGGER.warn("get Method from interface failed", e); - return invocation.getMethod(); - } - } - - /** - * get the interface of proxy - * - * @param proxyBean the proxy bean - * @return proxy interface - * @throws Exception the exception - */ - @Nullable - protected Class getProxyInterface(Object proxyBean) throws Exception { - if (DubboUtil.isDubboProxyName(proxyBean.getClass().getName())) { - //dubbo javaassist proxy - return DubboUtil.getAssistInterface(proxyBean); - } else { - //jdk/cglib proxy - return SpringProxyUtils.getTargetInterface(proxyBean); - } - } - @Override public void onChangeEvent(ConfigurationChangeEvent event) { if (ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION.equals(event.getDataId())) { diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParser.java b/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParser.java index c1a36c8b9ec..be56b923ce2 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParser.java +++ b/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParser.java @@ -20,8 +20,9 @@ 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; +import io.seata.spring.remoting.Protocols; +import io.seata.spring.remoting.RemotingDesc; +import io.seata.spring.remoting.parser.AbstractedRemotingParser; /** * local tcc bean parsing @@ -46,7 +47,8 @@ public RemotingDesc getServiceDesc(Object bean, String beanName) throws Framewor return null; } RemotingDesc remotingDesc = new RemotingDesc(); - remotingDesc.setReference(true); + remotingDesc.setReference(this.isReference(bean, beanName)); + remotingDesc.setService(this.isService(bean, beanName)); remotingDesc.setProtocol(Protocols.IN_JVM); Class classType = bean.getClass(); Set> interfaceClasses = ReflectionUtil.getInterfaces(classType); diff --git a/tcc/src/main/resources/META-INF/services/io.seata.rm.tcc.remoting.RemotingParser b/tcc/src/main/resources/META-INF/services/io.seata.rm.tcc.remoting.RemotingParser deleted file mode 100644 index 30ef923192e..00000000000 --- a/tcc/src/main/resources/META-INF/services/io.seata.rm.tcc.remoting.RemotingParser +++ /dev/null @@ -1,4 +0,0 @@ -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 \ No newline at end of file diff --git a/tcc/src/test/java/io/seata/rm/tcc/interceptor/ActionInterceptorHandlerTest.java b/tcc/src/test/java/io/seata/rm/tcc/interceptor/ActionInterceptorHandlerTest.java index 1fb8f1b5fb8..f3ae613f2c2 100644 --- a/tcc/src/test/java/io/seata/rm/tcc/interceptor/ActionInterceptorHandlerTest.java +++ b/tcc/src/test/java/io/seata/rm/tcc/interceptor/ActionInterceptorHandlerTest.java @@ -18,6 +18,7 @@ import io.seata.rm.tcc.TccAction; import io.seata.rm.tcc.TccParam; import io.seata.rm.tcc.api.BusinessActionContext; +import io.seata.spring.interceptor.ActionInterceptorHandler; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/tcc/src/test/java/io/seata/rm/tcc/remoting/parser/DefaultRemotingParserTest.java b/tcc/src/test/java/io/seata/rm/tcc/remoting/parser/DefaultRemotingParserTest.java index 33e493af265..13f9ae38e48 100644 --- a/tcc/src/test/java/io/seata/rm/tcc/remoting/parser/DefaultRemotingParserTest.java +++ b/tcc/src/test/java/io/seata/rm/tcc/remoting/parser/DefaultRemotingParserTest.java @@ -17,6 +17,7 @@ import io.seata.rm.tcc.TccParam; import io.seata.rm.tcc.api.BusinessActionContext; +import io.seata.spring.remoting.parser.DefaultRemotingParser; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -24,7 +25,7 @@ public class DefaultRemotingParserTest { - DefaultRemotingParser defaultRemotingParser = new DefaultRemotingParser(); + DefaultRemotingParser defaultRemotingParser = DefaultRemotingParser.get(); @Test public void testGetTwoPhaseArgs() throws Exception { diff --git a/tcc/src/test/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParserTest.java b/tcc/src/test/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParserTest.java index f8cae88b070..87454ce64ed 100644 --- a/tcc/src/test/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParserTest.java +++ b/tcc/src/test/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParserTest.java @@ -17,7 +17,7 @@ import io.seata.rm.tcc.TccAction; import io.seata.rm.tcc.TccActionImpl; -import io.seata.rm.tcc.remoting.RemotingDesc; +import io.seata.spring.remoting.RemotingDesc; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/spring/src/test/java/io/seata/spring/annotation/Business.java b/tcc/src/test/java/io/seata/rm/tcc/spring/Business.java similarity index 95% rename from spring/src/test/java/io/seata/spring/annotation/Business.java rename to tcc/src/test/java/io/seata/rm/tcc/spring/Business.java index aaa351d03a3..2ad498f4d9e 100644 --- a/spring/src/test/java/io/seata/spring/annotation/Business.java +++ b/tcc/src/test/java/io/seata/rm/tcc/spring/Business.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.annotation; +package io.seata.rm.tcc.spring; /** * The interface Business. diff --git a/spring/src/test/java/io/seata/spring/annotation/BusinessImpl.java b/tcc/src/test/java/io/seata/rm/tcc/spring/BusinessImpl.java similarity index 92% rename from spring/src/test/java/io/seata/spring/annotation/BusinessImpl.java rename to tcc/src/test/java/io/seata/rm/tcc/spring/BusinessImpl.java index 2ac53976727..15c8e5b27c1 100644 --- a/spring/src/test/java/io/seata/spring/annotation/BusinessImpl.java +++ b/tcc/src/test/java/io/seata/rm/tcc/spring/BusinessImpl.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.annotation; +package io.seata.rm.tcc.spring; +import io.seata.spring.annotation.GlobalTransactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/spring/src/test/java/io/seata/spring/annotation/BusinessProxy.java b/tcc/src/test/java/io/seata/rm/tcc/spring/BusinessProxy.java similarity index 97% rename from spring/src/test/java/io/seata/spring/annotation/BusinessProxy.java rename to tcc/src/test/java/io/seata/rm/tcc/spring/BusinessProxy.java index 14e0043e06a..6eb4cc798e8 100644 --- a/spring/src/test/java/io/seata/spring/annotation/BusinessProxy.java +++ b/tcc/src/test/java/io/seata/rm/tcc/spring/BusinessProxy.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.annotation; +package io.seata.rm.tcc.spring; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; diff --git a/tcc/src/test/java/io/seata/rm/tcc/spring/GlobalTransactionScannerSub.java b/tcc/src/test/java/io/seata/rm/tcc/spring/GlobalTransactionScannerSub.java new file mode 100644 index 00000000000..1db26b3b48d --- /dev/null +++ b/tcc/src/test/java/io/seata/rm/tcc/spring/GlobalTransactionScannerSub.java @@ -0,0 +1,18 @@ +package io.seata.rm.tcc.spring; + +import io.seata.spring.annotation.GlobalTransactionScanner; + +/** + * the subclass of GlobalTransactionScanner for test public method wrapIfNecessary + */ +public class GlobalTransactionScannerSub extends GlobalTransactionScanner { + + + public GlobalTransactionScannerSub(String txServiceGroup) { + super(txServiceGroup); + } + + public Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { + return super.wrapIfNecessary(bean, beanName, cacheKey); + } +} diff --git a/spring/src/test/java/io/seata/spring/annotation/GlobalTransactionScannerTest.java b/tcc/src/test/java/io/seata/rm/tcc/spring/GlobalTransactionScannerTest.java similarity index 91% rename from spring/src/test/java/io/seata/spring/annotation/GlobalTransactionScannerTest.java rename to tcc/src/test/java/io/seata/rm/tcc/spring/GlobalTransactionScannerTest.java index 1ab5b9927be..523247ab6c5 100644 --- a/spring/src/test/java/io/seata/spring/annotation/GlobalTransactionScannerTest.java +++ b/tcc/src/test/java/io/seata/rm/tcc/spring/GlobalTransactionScannerTest.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.annotation; +package io.seata.rm.tcc.spring; -import io.seata.spring.tcc.LocalTccAction; -import io.seata.spring.tcc.LocalTccActionImpl; -import io.seata.spring.tcc.TccAction; -import io.seata.spring.tcc.TccActionImpl; +import io.seata.rm.tcc.spring.tcc.LocalTccAction; +import io.seata.rm.tcc.spring.tcc.LocalTccActionImpl; +import io.seata.rm.tcc.spring.tcc.TccAction; +import io.seata.rm.tcc.spring.tcc.TccActionImpl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -34,7 +34,7 @@ public class GlobalTransactionScannerTest { /** * The Global transaction scanner. */ - protected GlobalTransactionScanner globalTransactionScanner = new GlobalTransactionScanner("global-trans-scanner-test"); + protected GlobalTransactionScannerSub globalTransactionScanner = new GlobalTransactionScannerSub("global-trans-scanner-test"); /** * Test wrap normal bean. diff --git a/spring/src/test/java/io/seata/spring/tcc/LocalTccAction.java b/tcc/src/test/java/io/seata/rm/tcc/spring/tcc/LocalTccAction.java similarity index 95% rename from spring/src/test/java/io/seata/spring/tcc/LocalTccAction.java rename to tcc/src/test/java/io/seata/rm/tcc/spring/tcc/LocalTccAction.java index 4fdeb378a18..a3a2fb33473 100644 --- a/spring/src/test/java/io/seata/spring/tcc/LocalTccAction.java +++ b/tcc/src/test/java/io/seata/rm/tcc/spring/tcc/LocalTccAction.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.tcc; +package io.seata.rm.tcc.spring.tcc; import io.seata.rm.tcc.api.LocalTCC; diff --git a/spring/src/test/java/io/seata/spring/tcc/LocalTccActionImpl.java b/tcc/src/test/java/io/seata/rm/tcc/spring/tcc/LocalTccActionImpl.java similarity index 95% rename from spring/src/test/java/io/seata/spring/tcc/LocalTccActionImpl.java rename to tcc/src/test/java/io/seata/rm/tcc/spring/tcc/LocalTccActionImpl.java index e693c1e92cc..2b9c4091b70 100644 --- a/spring/src/test/java/io/seata/spring/tcc/LocalTccActionImpl.java +++ b/tcc/src/test/java/io/seata/rm/tcc/spring/tcc/LocalTccActionImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.tcc; +package io.seata.rm.tcc.spring.tcc; /** * The type Local tcc action. diff --git a/spring/src/test/java/io/seata/spring/tcc/TccAction.java b/tcc/src/test/java/io/seata/rm/tcc/spring/tcc/TccAction.java similarity index 97% rename from spring/src/test/java/io/seata/spring/tcc/TccAction.java rename to tcc/src/test/java/io/seata/rm/tcc/spring/tcc/TccAction.java index db39fb708f5..b89e6fcfaba 100644 --- a/spring/src/test/java/io/seata/spring/tcc/TccAction.java +++ b/tcc/src/test/java/io/seata/rm/tcc/spring/tcc/TccAction.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.tcc; +package io.seata.rm.tcc.spring.tcc; import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; diff --git a/spring/src/test/java/io/seata/spring/tcc/TccActionImpl.java b/tcc/src/test/java/io/seata/rm/tcc/spring/tcc/TccActionImpl.java similarity index 96% rename from spring/src/test/java/io/seata/spring/tcc/TccActionImpl.java rename to tcc/src/test/java/io/seata/rm/tcc/spring/tcc/TccActionImpl.java index c28a844c4ae..31b7f594b76 100644 --- a/spring/src/test/java/io/seata/spring/tcc/TccActionImpl.java +++ b/tcc/src/test/java/io/seata/rm/tcc/spring/tcc/TccActionImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.seata.spring.tcc; +package io.seata.rm.tcc.spring.tcc; import io.seata.rm.tcc.api.BusinessActionContext;