chore(project): 添加项目配置文件和忽略规则
- 添加 Babel 配置文件支持 ES6+ 语法转换 - 添加 ESLint 忽略规则和配置文件 - 添加 Git 忽略规则文件 - 添加 Travis CI 配置文件 - 添加 1.4.2 版本变更日志文件 - 添加 Helm 图表辅助模板文件 - 添加 Helm 忽略规则文件
This commit is contained in:
82
spring/pom.xml
Normal file
82
spring/pom.xml
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-spring</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>seata-spring ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-tm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-rm-datasource</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-tcc</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-rm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-serializer-all</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
/**
|
||||
* declare the transaction only execute in single local RM
|
||||
* but the transaction need to ensure records to update(or select for update) is not in global transaction middle
|
||||
* stage
|
||||
*
|
||||
* use this annotation instead of GlobalTransaction in the situation mentioned above will help performance.
|
||||
*
|
||||
* @see io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary(Object, String, Object) // the scanner for TM, GlobalLock, and TCC mode
|
||||
* @see io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalLock(MethodInvocation) // the interceptor of GlobalLock
|
||||
* @see io.seata.spring.annotation.datasource.SeataAutoDataSourceProxyAdvice#invoke(MethodInvocation) // the interceptor of GlobalLockLogic and AT/XA mode
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||
@Inherited
|
||||
public @interface GlobalLock {
|
||||
/**
|
||||
* customized global lock retry internal(unit: ms)
|
||||
* you may use this to override global config of "client.rm.lock.retryInterval"
|
||||
* note: 0 or negative number will take no effect(which mean fall back to global config)
|
||||
* @return lock retry internal
|
||||
*/
|
||||
int lockRetryInternal() default 0;
|
||||
|
||||
/**
|
||||
* customized global lock retry times
|
||||
* you may use this to override global config of "client.rm.lock.retryTimes"
|
||||
* note: negative number will take no effect(which mean fall back to global config)
|
||||
* @return lock retry times
|
||||
*/
|
||||
int lockRetryTimes() default -1;
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* 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 java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.ConfigurationChangeEvent;
|
||||
import io.seata.config.ConfigurationChangeListener;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.config.ConfigurationCache;
|
||||
import io.seata.core.constants.ConfigurationKeys;
|
||||
import io.seata.core.rpc.netty.RmNettyRemotingClient;
|
||||
import io.seata.core.rpc.ShutdownHook;
|
||||
import io.seata.core.rpc.netty.TmNettyRemotingClient;
|
||||
import io.seata.rm.RMClient;
|
||||
import io.seata.spring.tcc.TccActionInterceptor;
|
||||
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.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.framework.AdvisedSupport;
|
||||
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
|
||||
import static io.seata.common.DefaultValues.DEFAULT_DISABLE_GLOBAL_TRANSACTION;
|
||||
|
||||
/**
|
||||
* The type Global transaction scanner.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class GlobalTransactionScanner extends AbstractAutoProxyCreator
|
||||
implements ConfigurationChangeListener, InitializingBean, ApplicationContextAware, DisposableBean {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalTransactionScanner.class);
|
||||
|
||||
private static final int AT_MODE = 1;
|
||||
private static final int MT_MODE = 2;
|
||||
|
||||
private static final int ORDER_NUM = 1024;
|
||||
private static final int DEFAULT_MODE = AT_MODE + MT_MODE;
|
||||
|
||||
private static final Set<String> PROXYED_SET = new HashSet<>();
|
||||
|
||||
private MethodInterceptor interceptor;
|
||||
private MethodInterceptor globalTransactionalInterceptor;
|
||||
|
||||
private final String applicationId;
|
||||
private final String txServiceGroup;
|
||||
private final int mode;
|
||||
private String accessKey;
|
||||
private String secretKey;
|
||||
private volatile boolean disableGlobalTransaction = ConfigurationFactory.getInstance().getBoolean(
|
||||
ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, DEFAULT_DISABLE_GLOBAL_TRANSACTION);
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
private final FailureHandler failureHandlerHook;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* Instantiates a new Global transaction scanner.
|
||||
*
|
||||
* @param txServiceGroup the tx service group
|
||||
*/
|
||||
public GlobalTransactionScanner(String txServiceGroup) {
|
||||
this(txServiceGroup, txServiceGroup, DEFAULT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Global transaction scanner.
|
||||
*
|
||||
* @param txServiceGroup the tx service group
|
||||
* @param mode the mode
|
||||
*/
|
||||
public GlobalTransactionScanner(String txServiceGroup, int mode) {
|
||||
this(txServiceGroup, txServiceGroup, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Global transaction scanner.
|
||||
*
|
||||
* @param applicationId the application id
|
||||
* @param txServiceGroup the default server group
|
||||
*/
|
||||
public GlobalTransactionScanner(String applicationId, String txServiceGroup) {
|
||||
this(applicationId, txServiceGroup, DEFAULT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Global transaction scanner.
|
||||
*
|
||||
* @param applicationId the application id
|
||||
* @param txServiceGroup the tx service group
|
||||
* @param mode the mode
|
||||
*/
|
||||
public GlobalTransactionScanner(String applicationId, String txServiceGroup, int mode) {
|
||||
this(applicationId, txServiceGroup, mode, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Global transaction scanner.
|
||||
*
|
||||
* @param applicationId the application id
|
||||
* @param txServiceGroup the tx service group
|
||||
* @param failureHandlerHook the failure handler hook
|
||||
*/
|
||||
public GlobalTransactionScanner(String applicationId, String txServiceGroup, FailureHandler failureHandlerHook) {
|
||||
this(applicationId, txServiceGroup, DEFAULT_MODE, failureHandlerHook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Global transaction scanner.
|
||||
*
|
||||
* @param applicationId the application id
|
||||
* @param txServiceGroup the tx service group
|
||||
* @param mode the mode
|
||||
* @param failureHandlerHook the failure handler hook
|
||||
*/
|
||||
public GlobalTransactionScanner(String applicationId, String txServiceGroup, int mode,
|
||||
FailureHandler failureHandlerHook) {
|
||||
setOrder(ORDER_NUM);
|
||||
setProxyTargetClass(true);
|
||||
this.applicationId = applicationId;
|
||||
this.txServiceGroup = txServiceGroup;
|
||||
this.mode = mode;
|
||||
this.failureHandlerHook = failureHandlerHook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets access key.
|
||||
*
|
||||
* @param accessKey the access key
|
||||
*/
|
||||
public void setAccessKey(String accessKey) {
|
||||
this.accessKey = accessKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets secret key.
|
||||
*
|
||||
* @param secretKey the secret key
|
||||
*/
|
||||
public void setSecretKey(String secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
ShutdownHook.getInstance().destroyAll();
|
||||
}
|
||||
|
||||
private void initClient() {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Initializing Global Transaction Clients ... ");
|
||||
}
|
||||
if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
|
||||
throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));
|
||||
}
|
||||
//init TM
|
||||
TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
|
||||
}
|
||||
//init RM
|
||||
RMClient.init(applicationId, txServiceGroup);
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
|
||||
}
|
||||
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Global Transaction Clients are initialized. ");
|
||||
}
|
||||
registerSpringShutdownHook();
|
||||
|
||||
}
|
||||
|
||||
private void registerSpringShutdownHook() {
|
||||
if (applicationContext instanceof ConfigurableApplicationContext) {
|
||||
((ConfigurableApplicationContext) applicationContext).registerShutdownHook();
|
||||
ShutdownHook.removeRuntimeShutdownHook();
|
||||
}
|
||||
ShutdownHook.getInstance().addDisposable(TmNettyRemotingClient.getInstance(applicationId, txServiceGroup));
|
||||
ShutdownHook.getInstance().addDisposable(RmNettyRemotingClient.getInstance(applicationId, txServiceGroup));
|
||||
}
|
||||
|
||||
/**
|
||||
* The following will be scanned, and added corresponding interceptor:
|
||||
*
|
||||
* TM:
|
||||
* @see io.seata.spring.annotation.GlobalTransactional // TM annotation
|
||||
* Corresponding interceptor:
|
||||
* @see io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalTransaction(MethodInvocation, GlobalTransactional) // TM handler
|
||||
*
|
||||
* GlobalLock:
|
||||
* @see io.seata.spring.annotation.GlobalLock // GlobalLock annotation
|
||||
* Corresponding interceptor:
|
||||
* @see io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalLock(MethodInvocation, GlobalLock) // GlobalLock handler
|
||||
*
|
||||
* 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
|
||||
* Corresponding interceptor:
|
||||
* @see io.seata.spring.tcc.TccActionInterceptor // the interceptor of TCC mode
|
||||
*/
|
||||
@Override
|
||||
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
|
||||
try {
|
||||
synchronized (PROXYED_SET) {
|
||||
if (PROXYED_SET.contains(beanName)) {
|
||||
return bean;
|
||||
}
|
||||
interceptor = null;
|
||||
//check TCC proxy
|
||||
if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
|
||||
//TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC
|
||||
interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
|
||||
ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
|
||||
(ConfigurationChangeListener)interceptor);
|
||||
} else {
|
||||
Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
|
||||
Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
|
||||
|
||||
if (!existsAnnotation(new Class[]{serviceInterface})
|
||||
&& !existsAnnotation(interfacesIfJdk)) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
if (globalTransactionalInterceptor == null) {
|
||||
globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
|
||||
ConfigurationCache.addConfigListener(
|
||||
ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
|
||||
(ConfigurationChangeListener)globalTransactionalInterceptor);
|
||||
}
|
||||
interceptor = globalTransactionalInterceptor;
|
||||
}
|
||||
|
||||
LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.getClass().getName());
|
||||
if (!AopUtils.isAopProxy(bean)) {
|
||||
bean = super.wrapIfNecessary(bean, beanName, cacheKey);
|
||||
} else {
|
||||
AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
|
||||
Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
|
||||
for (Advisor avr : advisor) {
|
||||
advised.addAdvisor(0, avr);
|
||||
}
|
||||
}
|
||||
PROXYED_SET.add(beanName);
|
||||
return bean;
|
||||
}
|
||||
} catch (Exception exx) {
|
||||
throw new RuntimeException(exx);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean existsAnnotation(Class<?>[] classes) {
|
||||
if (CollectionUtils.isNotEmpty(classes)) {
|
||||
for (Class<?> clazz : classes) {
|
||||
if (clazz == null) {
|
||||
continue;
|
||||
}
|
||||
GlobalTransactional trxAnno = clazz.getAnnotation(GlobalTransactional.class);
|
||||
if (trxAnno != null) {
|
||||
return true;
|
||||
}
|
||||
Method[] methods = clazz.getMethods();
|
||||
for (Method method : methods) {
|
||||
trxAnno = method.getAnnotation(GlobalTransactional.class);
|
||||
if (trxAnno != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GlobalLock lockAnno = method.getAnnotation(GlobalLock.class);
|
||||
if (lockAnno != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private MethodDesc makeMethodDesc(GlobalTransactional anno, Method method) {
|
||||
return new MethodDesc(anno, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource customTargetSource)
|
||||
throws BeansException {
|
||||
return new Object[]{interceptor};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (disableGlobalTransaction) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Global transaction is disabled.");
|
||||
}
|
||||
ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
|
||||
(ConfigurationChangeListener)this);
|
||||
return;
|
||||
}
|
||||
if (initialized.compareAndSet(false, true)) {
|
||||
initClient();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
this.setBeanFactory(applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeEvent(ConfigurationChangeEvent event) {
|
||||
if (ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION.equals(event.getDataId())) {
|
||||
disableGlobalTransaction = Boolean.parseBoolean(event.getNewValue().trim());
|
||||
if (!disableGlobalTransaction && initialized.compareAndSet(false, true)) {
|
||||
LOGGER.info("{} config changed, old value:{}, new value:{}", ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
|
||||
disableGlobalTransaction, event.getNewValue());
|
||||
initClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import io.seata.common.DefaultValues;
|
||||
import io.seata.tm.api.transaction.Propagation;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
/**
|
||||
* The interface Global transactional.
|
||||
*
|
||||
* @author slievrly
|
||||
* @see io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary(Object, String, Object) io.seata.spring
|
||||
* .annotation.GlobalTransactionScanner#wrapIfNecessary(Object, String, Object)// the scanner for TM, GlobalLock, and
|
||||
* TCC mode
|
||||
* @see io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalTransaction(MethodInvocation,
|
||||
* GlobalTransactional) io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalTransaction
|
||||
* (MethodInvocation,
|
||||
* GlobalTransactional)// TM: the interceptor of TM
|
||||
* @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
|
||||
* .TccActionInterceptor#invoke(MethodInvocation)// RM: the interceptor of TCC mode
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||
@Inherited
|
||||
public @interface GlobalTransactional {
|
||||
|
||||
/**
|
||||
* Global transaction timeoutMills in MILLISECONDS.
|
||||
* If client.tm.default-global-transaction-timeout is configured, It will replace the DefaultValues
|
||||
* .DEFAULT_GLOBAL_TRANSACTION_TIMEOUT.
|
||||
*
|
||||
* @return timeoutMills in MILLISECONDS.
|
||||
*/
|
||||
int timeoutMills() default DefaultValues.DEFAULT_GLOBAL_TRANSACTION_TIMEOUT;
|
||||
|
||||
/**
|
||||
* Given name of the global transaction instance.
|
||||
*
|
||||
* @return Given name.
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* roll back for the Class
|
||||
*
|
||||
* @return the class array of the rollback for
|
||||
*/
|
||||
Class<? extends Throwable>[] rollbackFor() default {};
|
||||
|
||||
/**
|
||||
* roll back for the class name
|
||||
*
|
||||
* @return the class name of rollback for
|
||||
*/
|
||||
String[] rollbackForClassName() default {};
|
||||
|
||||
/**
|
||||
* not roll back for the Class
|
||||
*
|
||||
* @return the class array of no rollback for
|
||||
*/
|
||||
Class<? extends Throwable>[] noRollbackFor() default {};
|
||||
|
||||
/**
|
||||
* not roll back for the class name
|
||||
*
|
||||
* @return string [ ]
|
||||
*/
|
||||
String[] noRollbackForClassName() default {};
|
||||
|
||||
/**
|
||||
* the propagation of the global transaction
|
||||
*
|
||||
* @return propagation
|
||||
*/
|
||||
Propagation propagation() default Propagation.REQUIRED;
|
||||
|
||||
/**
|
||||
* customized global lock retry internal(unit: ms)
|
||||
* you may use this to override global config of "client.rm.lock.retryInterval"
|
||||
* note: 0 or negative number will take no effect(which mean fall back to global config)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int lockRetryInternal() default 0;
|
||||
|
||||
/**
|
||||
* customized global lock retry times
|
||||
* you may use this to override global config of "client.rm.lock.retryTimes"
|
||||
* note: negative number will take no effect(which mean fall back to global config)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int lockRetryTimes() default -1;
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.ConfigurationCache;
|
||||
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.event.EventBus;
|
||||
import io.seata.core.event.GuavaEventBus;
|
||||
import io.seata.core.model.GlobalLockConfig;
|
||||
import io.seata.spring.event.DegradeCheckEvent;
|
||||
import io.seata.tm.TransactionManagerHolder;
|
||||
import io.seata.tm.api.DefaultFailureHandlerImpl;
|
||||
import io.seata.tm.api.FailureHandler;
|
||||
import io.seata.rm.GlobalLockExecutor;
|
||||
import io.seata.rm.GlobalLockTemplate;
|
||||
import io.seata.tm.api.TransactionalExecutor;
|
||||
import io.seata.tm.api.TransactionalTemplate;
|
||||
import io.seata.tm.api.transaction.NoRollbackRule;
|
||||
import io.seata.tm.api.transaction.RollbackRule;
|
||||
import io.seata.tm.api.transaction.TransactionInfo;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static io.seata.common.DefaultValues.DEFAULT_DISABLE_GLOBAL_TRANSACTION;
|
||||
import static io.seata.common.DefaultValues.DEFAULT_GLOBAL_TRANSACTION_TIMEOUT;
|
||||
import static io.seata.common.DefaultValues.DEFAULT_TM_DEGRADE_CHECK;
|
||||
import static io.seata.common.DefaultValues.DEFAULT_TM_DEGRADE_CHECK_ALLOW_TIMES;
|
||||
import static io.seata.common.DefaultValues.DEFAULT_TM_DEGRADE_CHECK_PERIOD;
|
||||
|
||||
/**
|
||||
* The type Global transactional interceptor.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class GlobalTransactionalInterceptor implements ConfigurationChangeListener, MethodInterceptor {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalTransactionalInterceptor.class);
|
||||
private static final FailureHandler DEFAULT_FAIL_HANDLER = new DefaultFailureHandlerImpl();
|
||||
|
||||
private final TransactionalTemplate transactionalTemplate = new TransactionalTemplate();
|
||||
private final GlobalLockTemplate globalLockTemplate = new GlobalLockTemplate();
|
||||
private final FailureHandler failureHandler;
|
||||
private volatile boolean disable;
|
||||
private static int degradeCheckPeriod;
|
||||
private static volatile boolean degradeCheck;
|
||||
private static int degradeCheckAllowTimes;
|
||||
private static volatile Integer degradeNum = 0;
|
||||
private static volatile Integer reachNum = 0;
|
||||
private static final EventBus EVENT_BUS = new GuavaEventBus("degradeCheckEventBus", true);
|
||||
private static ScheduledThreadPoolExecutor executor =
|
||||
new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("degradeCheckWorker", 1, true));
|
||||
|
||||
//region DEFAULT_GLOBAL_TRANSACTION_TIMEOUT
|
||||
|
||||
private static int defaultGlobalTransactionTimeout = 0;
|
||||
|
||||
private void initDefaultGlobalTransactionTimeout() {
|
||||
if (GlobalTransactionalInterceptor.defaultGlobalTransactionTimeout <= 0) {
|
||||
int defaultGlobalTransactionTimeout;
|
||||
try {
|
||||
defaultGlobalTransactionTimeout = ConfigurationFactory.getInstance().getInt(
|
||||
ConfigurationKeys.DEFAULT_GLOBAL_TRANSACTION_TIMEOUT, DEFAULT_GLOBAL_TRANSACTION_TIMEOUT);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Illegal global transaction timeout value: " + e.getMessage());
|
||||
defaultGlobalTransactionTimeout = DEFAULT_GLOBAL_TRANSACTION_TIMEOUT;
|
||||
}
|
||||
if (defaultGlobalTransactionTimeout <= 0) {
|
||||
LOGGER.warn("Global transaction timeout value '{}' is illegal, and has been reset to the default value '{}'",
|
||||
defaultGlobalTransactionTimeout, DEFAULT_GLOBAL_TRANSACTION_TIMEOUT);
|
||||
defaultGlobalTransactionTimeout = DEFAULT_GLOBAL_TRANSACTION_TIMEOUT;
|
||||
}
|
||||
GlobalTransactionalInterceptor.defaultGlobalTransactionTimeout = defaultGlobalTransactionTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
/**
|
||||
* Instantiates a new Global transactional interceptor.
|
||||
*
|
||||
* @param failureHandler
|
||||
* the failure handler
|
||||
*/
|
||||
public GlobalTransactionalInterceptor(FailureHandler failureHandler) {
|
||||
this.failureHandler = failureHandler == null ? DEFAULT_FAIL_HANDLER : failureHandler;
|
||||
this.disable = ConfigurationFactory.getInstance().getBoolean(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
|
||||
DEFAULT_DISABLE_GLOBAL_TRANSACTION);
|
||||
degradeCheck = ConfigurationFactory.getInstance().getBoolean(ConfigurationKeys.CLIENT_DEGRADE_CHECK,
|
||||
DEFAULT_TM_DEGRADE_CHECK);
|
||||
if (degradeCheck) {
|
||||
ConfigurationCache.addConfigListener(ConfigurationKeys.CLIENT_DEGRADE_CHECK, this);
|
||||
degradeCheckPeriod = ConfigurationFactory.getInstance().getInt(
|
||||
ConfigurationKeys.CLIENT_DEGRADE_CHECK_PERIOD, DEFAULT_TM_DEGRADE_CHECK_PERIOD);
|
||||
degradeCheckAllowTimes = ConfigurationFactory.getInstance().getInt(
|
||||
ConfigurationKeys.CLIENT_DEGRADE_CHECK_ALLOW_TIMES, DEFAULT_TM_DEGRADE_CHECK_ALLOW_TIMES);
|
||||
EVENT_BUS.register(this);
|
||||
if (degradeCheckPeriod > 0 && degradeCheckAllowTimes > 0) {
|
||||
startDegradeCheck();
|
||||
}
|
||||
}
|
||||
this.initDefaultGlobalTransactionTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
|
||||
Class<?> targetClass =
|
||||
methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;
|
||||
Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
|
||||
if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
|
||||
final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
|
||||
final GlobalTransactional globalTransactionalAnnotation =
|
||||
getAnnotation(method, targetClass, GlobalTransactional.class);
|
||||
final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);
|
||||
boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);
|
||||
if (!localDisable) {
|
||||
if (globalTransactionalAnnotation != null) {
|
||||
return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);
|
||||
} else if (globalLockAnnotation != null) {
|
||||
return handleGlobalLock(methodInvocation, globalLockAnnotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
return methodInvocation.proceed();
|
||||
}
|
||||
|
||||
Object handleGlobalLock(final MethodInvocation methodInvocation,
|
||||
final GlobalLock globalLockAnno) throws Throwable {
|
||||
return globalLockTemplate.execute(new GlobalLockExecutor() {
|
||||
@Override
|
||||
public Object execute() throws Throwable {
|
||||
return methodInvocation.proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalLockConfig getGlobalLockConfig() {
|
||||
GlobalLockConfig config = new GlobalLockConfig();
|
||||
config.setLockRetryInternal(globalLockAnno.lockRetryInternal());
|
||||
config.setLockRetryTimes(globalLockAnno.lockRetryTimes());
|
||||
return config;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Object handleGlobalTransaction(final MethodInvocation methodInvocation,
|
||||
final GlobalTransactional globalTrxAnno) throws Throwable {
|
||||
boolean succeed = true;
|
||||
try {
|
||||
return transactionalTemplate.execute(new TransactionalExecutor() {
|
||||
@Override
|
||||
public Object execute() throws Throwable {
|
||||
return methodInvocation.proceed();
|
||||
}
|
||||
|
||||
public String name() {
|
||||
String name = globalTrxAnno.name();
|
||||
if (!StringUtils.isNullOrEmpty(name)) {
|
||||
return name;
|
||||
}
|
||||
return formatMethod(methodInvocation.getMethod());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionInfo getTransactionInfo() {
|
||||
// reset the value of timeout
|
||||
int timeout = globalTrxAnno.timeoutMills();
|
||||
if (timeout <= 0 || timeout == DEFAULT_GLOBAL_TRANSACTION_TIMEOUT) {
|
||||
timeout = defaultGlobalTransactionTimeout;
|
||||
}
|
||||
|
||||
TransactionInfo transactionInfo = new TransactionInfo();
|
||||
transactionInfo.setTimeOut(timeout);
|
||||
transactionInfo.setName(name());
|
||||
transactionInfo.setPropagation(globalTrxAnno.propagation());
|
||||
transactionInfo.setLockRetryInternal(globalTrxAnno.lockRetryInternal());
|
||||
transactionInfo.setLockRetryTimes(globalTrxAnno.lockRetryTimes());
|
||||
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
|
||||
for (Class<?> rbRule : globalTrxAnno.rollbackFor()) {
|
||||
rollbackRules.add(new RollbackRule(rbRule));
|
||||
}
|
||||
for (String rbRule : globalTrxAnno.rollbackForClassName()) {
|
||||
rollbackRules.add(new RollbackRule(rbRule));
|
||||
}
|
||||
for (Class<?> rbRule : globalTrxAnno.noRollbackFor()) {
|
||||
rollbackRules.add(new NoRollbackRule(rbRule));
|
||||
}
|
||||
for (String rbRule : globalTrxAnno.noRollbackForClassName()) {
|
||||
rollbackRules.add(new NoRollbackRule(rbRule));
|
||||
}
|
||||
transactionInfo.setRollbackRules(rollbackRules);
|
||||
return transactionInfo;
|
||||
}
|
||||
});
|
||||
} catch (TransactionalExecutor.ExecutionException e) {
|
||||
TransactionalExecutor.Code code = e.getCode();
|
||||
switch (code) {
|
||||
case RollbackDone:
|
||||
throw e.getOriginalException();
|
||||
case BeginFailure:
|
||||
succeed = false;
|
||||
failureHandler.onBeginFailure(e.getTransaction(), e.getCause());
|
||||
throw e.getCause();
|
||||
case CommitFailure:
|
||||
succeed = false;
|
||||
failureHandler.onCommitFailure(e.getTransaction(), e.getCause());
|
||||
throw e.getCause();
|
||||
case RollbackFailure:
|
||||
failureHandler.onRollbackFailure(e.getTransaction(), e.getOriginalException());
|
||||
throw e.getOriginalException();
|
||||
case RollbackRetrying:
|
||||
failureHandler.onRollbackRetrying(e.getTransaction(), e.getOriginalException());
|
||||
throw e.getOriginalException();
|
||||
default:
|
||||
throw new ShouldNeverHappenException(String.format("Unknown TransactionalExecutor.Code: %s", code));
|
||||
}
|
||||
} finally {
|
||||
if (degradeCheck) {
|
||||
EVENT_BUS.post(new DegradeCheckEvent(succeed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends Annotation> T getAnnotation(Method method, Class<?> targetClass, Class<T> annotationClass) {
|
||||
return Optional.ofNullable(method).map(m -> m.getAnnotation(annotationClass))
|
||||
.orElse(Optional.ofNullable(targetClass).map(t -> t.getAnnotation(annotationClass)).orElse(null));
|
||||
}
|
||||
|
||||
private String formatMethod(Method method) {
|
||||
StringBuilder sb = new StringBuilder(method.getName()).append("(");
|
||||
|
||||
Class<?>[] params = method.getParameterTypes();
|
||||
int in = 0;
|
||||
for (Class<?> clazz : params) {
|
||||
sb.append(clazz.getName());
|
||||
if (++in < params.length) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
return sb.append(")").toString();
|
||||
}
|
||||
|
||||
@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());
|
||||
} else if (ConfigurationKeys.CLIENT_DEGRADE_CHECK.equals(event.getDataId())) {
|
||||
degradeCheck = Boolean.parseBoolean(event.getNewValue());
|
||||
if (!degradeCheck) {
|
||||
degradeNum = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* auto upgrade service detection
|
||||
*/
|
||||
private static void startDegradeCheck() {
|
||||
executor.scheduleAtFixedRate(() -> {
|
||||
if (degradeCheck) {
|
||||
try {
|
||||
String xid = TransactionManagerHolder.get().begin(null, null, "degradeCheck", 60000);
|
||||
TransactionManagerHolder.get().commit(xid);
|
||||
EVENT_BUS.post(new DegradeCheckEvent(true));
|
||||
} catch (Exception e) {
|
||||
EVENT_BUS.post(new DegradeCheckEvent(false));
|
||||
}
|
||||
}
|
||||
}, degradeCheckPeriod, degradeCheckPeriod, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public static void onDegradeCheck(DegradeCheckEvent event) {
|
||||
if (event.isRequestSuccess()) {
|
||||
if (degradeNum >= degradeCheckAllowTimes) {
|
||||
reachNum++;
|
||||
if (reachNum >= degradeCheckAllowTimes) {
|
||||
reachNum = 0;
|
||||
degradeNum = 0;
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("the current global transaction has been restored");
|
||||
}
|
||||
}
|
||||
} else if (degradeNum != 0) {
|
||||
degradeNum = 0;
|
||||
}
|
||||
} else {
|
||||
if (degradeNum < degradeCheckAllowTimes) {
|
||||
degradeNum++;
|
||||
if (degradeNum >= degradeCheckAllowTimes) {
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER.warn("the current global transaction has been automatically downgraded");
|
||||
}
|
||||
}
|
||||
} else if (reachNum != 0) {
|
||||
reachNum = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.seata.spring.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* The type Method desc.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class MethodDesc {
|
||||
private GlobalTransactional transactionAnnotation;
|
||||
private Method method;
|
||||
|
||||
/**
|
||||
* Instantiates a new Method desc.
|
||||
*
|
||||
* @param transactionAnnotation the transaction annotation
|
||||
* @param method the method
|
||||
*/
|
||||
public MethodDesc(GlobalTransactional transactionAnnotation, Method method) {
|
||||
this.transactionAnnotation = transactionAnnotation;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets transaction annotation.
|
||||
*
|
||||
* @return the transaction annotation
|
||||
*/
|
||||
public GlobalTransactional getTransactionAnnotation() {
|
||||
return transactionAnnotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets transaction annotation.
|
||||
*
|
||||
* @param transactionAnnotation the transaction annotation
|
||||
*/
|
||||
public void setTransactionAnnotation(GlobalTransactional transactionAnnotation) {
|
||||
this.transactionAnnotation = transactionAnnotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets method.
|
||||
*
|
||||
* @return the method
|
||||
*/
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets method.
|
||||
*
|
||||
* @param method the method
|
||||
*/
|
||||
public void setMethod(Method method) {
|
||||
this.method = method;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.datasource;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
* The type auto data source proxy registrar
|
||||
*/
|
||||
public class AutoDataSourceProxyRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
private static final String ATTRIBUTE_KEY_USE_JDK_PROXY = "useJdkProxy";
|
||||
private static final String ATTRIBUTE_KEY_EXCLUDES = "excludes";
|
||||
private static final String ATTRIBUTE_KEY_DATA_SOURCE_PROXY_MODE = "dataSourceProxyMode";
|
||||
|
||||
public static final String BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR = "seataDataSourceBeanPostProcessor";
|
||||
public static final String BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR = "seataAutoDataSourceProxyCreator";
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableAutoDataSourceProxy.class.getName());
|
||||
|
||||
boolean useJdkProxy = Boolean.parseBoolean(annotationAttributes.get(ATTRIBUTE_KEY_USE_JDK_PROXY).toString());
|
||||
String[] excludes = (String[]) annotationAttributes.get(ATTRIBUTE_KEY_EXCLUDES);
|
||||
String dataSourceProxyMode = (String) annotationAttributes.get(ATTRIBUTE_KEY_DATA_SOURCE_PROXY_MODE);
|
||||
|
||||
//register seataDataSourceBeanPostProcessor bean def
|
||||
if (!registry.containsBeanDefinition(BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR)) {
|
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(SeataDataSourceBeanPostProcessor.class)
|
||||
.addConstructorArgValue(excludes)
|
||||
.addConstructorArgValue(dataSourceProxyMode)
|
||||
.getBeanDefinition();
|
||||
registry.registerBeanDefinition(BEAN_NAME_SEATA_DATA_SOURCE_BEAN_POST_PROCESSOR, beanDefinition);
|
||||
}
|
||||
|
||||
//register seataAutoDataSourceProxyCreator bean def
|
||||
if (!registry.containsBeanDefinition(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR)) {
|
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(SeataAutoDataSourceProxyCreator.class)
|
||||
.addConstructorArgValue(useJdkProxy)
|
||||
.addConstructorArgValue(excludes)
|
||||
.addConstructorArgValue(dataSourceProxyMode)
|
||||
.getBeanDefinition();
|
||||
registry.registerBeanDefinition(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR, beanDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.datasource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.rm.datasource.DataSourceProxy;
|
||||
import io.seata.rm.datasource.SeataDataSourceProxy;
|
||||
import io.seata.rm.datasource.xa.DataSourceProxyXA;
|
||||
|
||||
/**
|
||||
* the type data source proxy holder
|
||||
*
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
public class DataSourceProxyHolder {
|
||||
private static final int MAP_INITIAL_CAPACITY = 8;
|
||||
private Map<DataSource, SeataDataSourceProxy> dataSourceProxyMap;
|
||||
|
||||
private DataSourceProxyHolder() {
|
||||
dataSourceProxyMap = new HashMap<>(MAP_INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* the type holder
|
||||
*/
|
||||
private static class Holder {
|
||||
private static final DataSourceProxyHolder INSTANCE;
|
||||
|
||||
static {
|
||||
INSTANCE = new DataSourceProxyHolder();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DataSourceProxyHolder instance
|
||||
*
|
||||
* @return the INSTANCE of DataSourceProxyHolder
|
||||
*/
|
||||
public static DataSourceProxyHolder get() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put dataSource
|
||||
*
|
||||
* @param dataSource the data source
|
||||
* @param dataSourceProxyMode the data source proxy mode
|
||||
* @return dataSourceProxy
|
||||
*/
|
||||
public SeataDataSourceProxy putDataSource(DataSource dataSource, BranchType dataSourceProxyMode) {
|
||||
DataSource originalDataSource;
|
||||
if (dataSource instanceof SeataDataSourceProxy) {
|
||||
SeataDataSourceProxy dataSourceProxy = (SeataDataSourceProxy) dataSource;
|
||||
|
||||
//If it's an right proxy, return it directly.
|
||||
if (dataSourceProxyMode == dataSourceProxy.getBranchType()) {
|
||||
return (SeataDataSourceProxy) dataSource;
|
||||
}
|
||||
|
||||
//Get the original data source.
|
||||
originalDataSource = dataSourceProxy.getTargetDataSource();
|
||||
} else {
|
||||
originalDataSource = dataSource;
|
||||
}
|
||||
|
||||
SeataDataSourceProxy dsProxy = dataSourceProxyMap.get(originalDataSource);
|
||||
if (dsProxy == null) {
|
||||
synchronized (dataSourceProxyMap) {
|
||||
dsProxy = dataSourceProxyMap.get(originalDataSource);
|
||||
if (dsProxy == null) {
|
||||
dsProxy = createDsProxyByMode(dataSourceProxyMode, originalDataSource);
|
||||
dataSourceProxyMap.put(originalDataSource, dsProxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dsProxy;
|
||||
}
|
||||
|
||||
private SeataDataSourceProxy createDsProxyByMode(BranchType mode, DataSource originDs) {
|
||||
return BranchType.XA == mode ? new DataSourceProxyXA(originDs) : new DataSourceProxy(originDs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.datasource;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
* This annotation will enable auto proxying of datasource bean.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(AutoDataSourceProxyRegistrar.class)
|
||||
@Documented
|
||||
public @interface EnableAutoDataSourceProxy {
|
||||
/**
|
||||
* Whether use JDK proxy instead of CGLIB proxy
|
||||
*
|
||||
* @return useJdkProxy
|
||||
*/
|
||||
boolean useJdkProxy() default false;
|
||||
|
||||
/**
|
||||
* Specifies which datasource bean are not eligible for auto-proxying
|
||||
*
|
||||
* @return excludes
|
||||
*/
|
||||
String[] excludes() default {};
|
||||
|
||||
/**
|
||||
* Data source proxy mode, AT or XA
|
||||
*
|
||||
* @return dataSourceProxyMode
|
||||
*/
|
||||
String dataSourceProxyMode() default "AT";
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.datasource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.rm.datasource.DataSourceProxy;
|
||||
import io.seata.rm.datasource.SeataDataSourceProxy;
|
||||
import io.seata.rm.datasource.xa.DataSourceProxyXA;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.IntroductionInfo;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
public class SeataAutoDataSourceProxyAdvice implements MethodInterceptor, IntroductionInfo {
|
||||
|
||||
private final BranchType dataSourceProxyMode;
|
||||
private final Class<? extends SeataDataSourceProxy> dataSourceProxyClazz;
|
||||
|
||||
public SeataAutoDataSourceProxyAdvice(String dataSourceProxyMode) {
|
||||
if (BranchType.AT.name().equalsIgnoreCase(dataSourceProxyMode)) {
|
||||
this.dataSourceProxyMode = BranchType.AT;
|
||||
this.dataSourceProxyClazz = DataSourceProxy.class;
|
||||
} else if (BranchType.XA.name().equalsIgnoreCase(dataSourceProxyMode)) {
|
||||
this.dataSourceProxyMode = BranchType.XA;
|
||||
this.dataSourceProxyClazz = DataSourceProxyXA.class;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown dataSourceProxyMode: " + dataSourceProxyMode);
|
||||
}
|
||||
|
||||
//Set the default branch type in the RootContext.
|
||||
RootContext.setDefaultBranchType(this.dataSourceProxyMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
if (!RootContext.requireGlobalLock() && dataSourceProxyMode != RootContext.getBranchType()) {
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
Method method = invocation.getMethod();
|
||||
Object[] args = invocation.getArguments();
|
||||
Method m = BeanUtils.findDeclaredMethod(dataSourceProxyClazz, method.getName(), method.getParameterTypes());
|
||||
if (m != null && DataSource.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
SeataDataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) invocation.getThis(), dataSourceProxyMode);
|
||||
return m.invoke(dataSourceProxy, args);
|
||||
} else {
|
||||
return invocation.proceed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?>[] getInterfaces() {
|
||||
return new Class[]{SeataProxy.class};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.datasource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
|
||||
import org.springframework.aop.support.DefaultIntroductionAdvisor;
|
||||
import org.springframework.beans.BeansException;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoDataSourceProxyCreator.class);
|
||||
private final List<String> excludes;
|
||||
private final Advisor advisor;
|
||||
|
||||
public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes, String dataSourceProxyMode) {
|
||||
this.excludes = Arrays.asList(excludes);
|
||||
this.advisor = new DefaultIntroductionAdvisor(new SeataAutoDataSourceProxyAdvice(dataSourceProxyMode));
|
||||
setProxyTargetClass(!useJdkProxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Auto proxy of [{}]", beanName);
|
||||
}
|
||||
return new Object[]{advisor};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
|
||||
return !DataSource.class.isAssignableFrom(beanClass) ||
|
||||
SeataProxy.class.isAssignableFrom(beanClass) ||
|
||||
excludes.contains(beanClass.getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.datasource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.rm.datasource.SeataDataSourceProxy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
|
||||
/**
|
||||
* The type seata data source bean post processor
|
||||
*
|
||||
* @author xingfudeshi@gmail.com
|
||||
* @author wang.liang
|
||||
*/
|
||||
public class SeataDataSourceBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SeataDataSourceBeanPostProcessor.class);
|
||||
|
||||
private final List<String> excludes;
|
||||
private final BranchType dataSourceProxyMode;
|
||||
|
||||
public SeataDataSourceBeanPostProcessor(String[] excludes, String dataSourceProxyMode) {
|
||||
this.excludes = Arrays.asList(excludes);
|
||||
this.dataSourceProxyMode = BranchType.XA.name().equalsIgnoreCase(dataSourceProxyMode) ? BranchType.XA : BranchType.AT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof DataSource) {
|
||||
//When not in the excludes, put and init proxy.
|
||||
if (!excludes.contains(bean.getClass().getName())) {
|
||||
//Only put and init proxy, not return proxy.
|
||||
DataSourceProxyHolder.get().putDataSource((DataSource) bean, dataSourceProxyMode);
|
||||
}
|
||||
|
||||
//If is SeataDataSourceProxy, return the original data source.
|
||||
if (bean instanceof SeataDataSourceProxy) {
|
||||
LOGGER.info("Unwrap the bean of the data source," +
|
||||
" and return the original data source to replace the data source proxy.");
|
||||
return ((SeataDataSourceProxy) bean).getTargetDataSource();
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.datasource;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
public interface SeataProxy {
|
||||
}
|
||||
@@ -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.event;
|
||||
|
||||
import io.seata.core.event.Event;
|
||||
|
||||
/**
|
||||
* @author slievrly
|
||||
*/
|
||||
public class DegradeCheckEvent implements Event {
|
||||
private boolean requestSuccess;
|
||||
|
||||
public DegradeCheckEvent(boolean requestSuccess) {
|
||||
this.requestSuccess = requestSuccess;
|
||||
}
|
||||
|
||||
public boolean isRequestSuccess() {
|
||||
return requestSuccess;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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.tcc;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import io.seata.common.Constants;
|
||||
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.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 org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import static io.seata.common.DefaultValues.DEFAULT_DISABLE_GLOBAL_TRANSACTION;
|
||||
|
||||
/**
|
||||
* TCC Interceptor
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class TccActionInterceptor implements MethodInterceptor, ConfigurationChangeListener {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TccActionInterceptor.class);
|
||||
|
||||
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 Tcc action interceptor.
|
||||
*/
|
||||
public TccActionInterceptor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Tcc action interceptor.
|
||||
*
|
||||
* @param remotingDesc the remoting desc
|
||||
*/
|
||||
public TccActionInterceptor(RemotingDesc remotingDesc) {
|
||||
this.remotingDesc = remotingDesc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(final MethodInvocation invocation) throws Throwable {
|
||||
if (!RootContext.inGlobalTransaction() || disable || RootContext.inSagaBranch()) {
|
||||
//not in transaction
|
||||
return invocation.proceed();
|
||||
}
|
||||
Method method = getActionInterfaceMethod(invocation);
|
||||
TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class);
|
||||
//try method
|
||||
if (businessAction != null) {
|
||||
//save the xid
|
||||
String xid = RootContext.getXID();
|
||||
//save the previous branchType
|
||||
BranchType previousBranchType = RootContext.getBranchType();
|
||||
//if not TCC, bind TCC branchType
|
||||
if (BranchType.TCC != previousBranchType) {
|
||||
RootContext.bindBranchType(BranchType.TCC);
|
||||
}
|
||||
try {
|
||||
Object[] methodArgs = invocation.getArguments();
|
||||
//Handler the TCC Aspect
|
||||
Map<String, Object> ret = actionInterceptorHandler.proceed(method, methodArgs, xid, businessAction,
|
||||
invocation::proceed);
|
||||
//return the final result
|
||||
return ret.get(Constants.TCC_METHOD_RESULT);
|
||||
}
|
||||
finally {
|
||||
//if not TCC, unbind branchType
|
||||
if (BranchType.TCC != previousBranchType) {
|
||||
RootContext.unbindBranchType();
|
||||
}
|
||||
//MDC remove branchId
|
||||
MDC.remove(RootContext.MDC_KEY_BRANCH_ID);
|
||||
}
|
||||
}
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.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 && !invocation.getMethod().getName().equals("toString")) {
|
||||
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
|
||||
*/
|
||||
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())) {
|
||||
LOGGER.info("{} config changed, old value:{}, new value:{}", ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
|
||||
disable, event.getNewValue());
|
||||
disable = Boolean.parseBoolean(event.getNewValue().trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.tcc;
|
||||
|
||||
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
|
||||
import io.seata.rm.tcc.remoting.RemotingDesc;
|
||||
import io.seata.spring.util.TCCBeanParserUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An annotation adapter for TCC
|
||||
*
|
||||
* @author ppf
|
||||
*/
|
||||
public class TccAnnotationProcessor implements BeanPostProcessor {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TccAnnotationProcessor.class);
|
||||
|
||||
private static final List<Class<? extends Annotation>> ANNOTATIONS = new ArrayList<>(4);
|
||||
private static final Set<String> PROXIED_SET = new HashSet<>();
|
||||
|
||||
static {
|
||||
ANNOTATIONS.add(loadAnnotation("org.apache.dubbo.config.annotation.Reference"));
|
||||
ANNOTATIONS.add(loadAnnotation("com.alipay.sofa.runtime.api.annotation.SofaReference"));
|
||||
}
|
||||
|
||||
private static Class<? extends Annotation> loadAnnotation(String annotation) {
|
||||
try {
|
||||
return (Class<? extends Annotation>) Class.forName(annotation);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process annotation
|
||||
*
|
||||
* @param bean the bean
|
||||
* @param beanName the bean name
|
||||
* @param annotation the annotation
|
||||
*/
|
||||
protected void process(Object bean, String beanName, Class<? extends Annotation> annotation) {
|
||||
if (Objects.isNull(annotation) || PROXIED_SET.contains(beanName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReflectionUtils.doWithFields(bean.getClass(), field -> {
|
||||
Annotation reference = field.getAnnotation(annotation);
|
||||
if (reference == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
addTccAdvise(bean, beanName, field, field.getType());
|
||||
|
||||
}, field -> !Modifier.isStatic(field.getModifiers())
|
||||
&& (field.isAnnotationPresent(annotation)));
|
||||
|
||||
PROXIED_SET.add(beanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add TCC interceptor for tcc proxy bean
|
||||
*
|
||||
* @param bean the bean
|
||||
* @param beanName the bean name
|
||||
* @param field the field
|
||||
* @param interfaceClass the interface class
|
||||
* @throws IllegalAccessException the illegal access exception
|
||||
*/
|
||||
public void addTccAdvise(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() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
for (Class<? extends Annotation> annotation : ANNOTATIONS) {
|
||||
process(bean, beanName, annotation);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
181
spring/src/main/java/io/seata/spring/util/SpringProxyUtils.java
Normal file
181
spring/src/main/java/io/seata/spring/util/SpringProxyUtils.java
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.rm.tcc.remoting.parser.DubboUtil;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.AdvisedSupport;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
|
||||
/**
|
||||
* Proxy tools base on spring
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class SpringProxyUtils {
|
||||
private SpringProxyUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find target class class.
|
||||
*
|
||||
* @param proxy the proxy
|
||||
* @return the class
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
public static Class<?> findTargetClass(Object proxy) throws Exception {
|
||||
if (proxy == null) {
|
||||
return null;
|
||||
}
|
||||
if (AopUtils.isAopProxy(proxy) && proxy instanceof Advised) {
|
||||
Object targetObject = ((Advised) proxy).getTargetSource().getTarget();
|
||||
return findTargetClass(targetObject);
|
||||
}
|
||||
return proxy.getClass();
|
||||
}
|
||||
|
||||
public static Class<?>[] findInterfaces(Object proxy) throws Exception {
|
||||
if (AopUtils.isJdkDynamicProxy(proxy)) {
|
||||
AdvisedSupport advised = getAdvisedSupport(proxy);
|
||||
return getInterfacesByAdvised(advised);
|
||||
} else {
|
||||
return new Class<?>[]{};
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?>[] getInterfacesByAdvised(AdvisedSupport advised) {
|
||||
Class<?>[] interfaces = advised.getProxiedInterfaces();
|
||||
if (interfaces.length > 0) {
|
||||
return interfaces;
|
||||
} else {
|
||||
throw new IllegalStateException("Find the jdk dynamic proxy class that does not implement the interface");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets advised support.
|
||||
*
|
||||
* @param proxy the proxy
|
||||
* @return the advised support
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
public static AdvisedSupport getAdvisedSupport(Object proxy) throws Exception {
|
||||
Field h;
|
||||
if (AopUtils.isJdkDynamicProxy(proxy)) {
|
||||
h = proxy.getClass().getSuperclass().getDeclaredField("h");
|
||||
} else {
|
||||
h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
|
||||
}
|
||||
h.setAccessible(true);
|
||||
Object dynamicAdvisedInterceptor = h.get(proxy);
|
||||
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
|
||||
advised.setAccessible(true);
|
||||
return (AdvisedSupport)advised.get(dynamicAdvisedInterceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is proxy boolean.
|
||||
*
|
||||
* @param bean the bean
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean isProxy(Object bean) {
|
||||
if (bean == null) {
|
||||
return false;
|
||||
}
|
||||
//check dubbo proxy ?
|
||||
return DubboUtil.isDubboProxyName(bean.getClass().getName()) || (Proxy.class.isAssignableFrom(bean.getClass())
|
||||
|| AopUtils.isAopProxy(bean));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target class , get the interface of its agent if it is a Proxy
|
||||
*
|
||||
* @param proxy the proxy
|
||||
* @return target interface
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
public static Class<?> getTargetInterface(Object proxy) throws Exception {
|
||||
if (proxy == null) {
|
||||
throw new java.lang.IllegalArgumentException("proxy can not be null");
|
||||
}
|
||||
|
||||
//jdk proxy
|
||||
if (Proxy.class.isAssignableFrom(proxy.getClass())) {
|
||||
Proxy p = (Proxy)proxy;
|
||||
return p.getClass().getInterfaces()[0];
|
||||
}
|
||||
|
||||
return getTargetClass(proxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class type of the proxy target object, if hadn't a target object, return the interface of the proxy
|
||||
*
|
||||
* @param proxy the proxy
|
||||
* @return target interface
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
protected static Class<?> getTargetClass(Object proxy) throws Exception {
|
||||
if (proxy == null) {
|
||||
throw new java.lang.IllegalArgumentException("proxy can not be null");
|
||||
}
|
||||
//not proxy
|
||||
if (!AopUtils.isAopProxy(proxy)) {
|
||||
return proxy.getClass();
|
||||
}
|
||||
AdvisedSupport advisedSupport = getAdvisedSupport(proxy);
|
||||
Object target = advisedSupport.getTargetSource().getTarget();
|
||||
/*
|
||||
* the Proxy of sofa:reference has no target
|
||||
*/
|
||||
if (target == null) {
|
||||
if (CollectionUtils.isNotEmpty(advisedSupport.getProxiedInterfaces())) {
|
||||
return advisedSupport.getProxiedInterfaces()[0];
|
||||
} else {
|
||||
return proxy.getClass();
|
||||
}
|
||||
} else {
|
||||
return getTargetClass(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the all interfaces of bean, if the bean is null, then return empty array
|
||||
* @param bean the bean
|
||||
* @return target interface
|
||||
*/
|
||||
public static Class<?>[] getAllInterfaces(Object bean) {
|
||||
Set<Class<?>> interfaces = new HashSet<>();
|
||||
if (bean != null) {
|
||||
Class<?> clazz = bean.getClass();
|
||||
while (!Object.class.getName().equalsIgnoreCase(clazz.getName())) {
|
||||
Class<?>[] clazzInterfaces = clazz.getInterfaces();
|
||||
interfaces.addAll(Arrays.asList(clazzInterfaces));
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
return interfaces.toArray(new Class[0]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
|
||||
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 org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* parser TCC bean
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class TCCBeanParserUtils {
|
||||
|
||||
private TCCBeanParserUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* is auto proxy TCC 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) {
|
||||
boolean isRemotingBean = parserRemotingServiceInfo(bean, beanName);
|
||||
//get RemotingBean description
|
||||
RemotingDesc remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(beanName);
|
||||
//is remoting bean
|
||||
if (isRemotingBean) {
|
||||
if (remotingDesc != null && remotingDesc.getProtocol() == Protocols.IN_JVM) {
|
||||
//LocalTCC
|
||||
return isTccProxyTargetBean(remotingDesc);
|
||||
} else {
|
||||
// sofa:reference / dubbo:reference, factory bean
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (remotingDesc == null) {
|
||||
//check FactoryBean
|
||||
if (isRemotingFactoryBean(bean, beanName, applicationContext)) {
|
||||
remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(beanName);
|
||||
return isTccProxyTargetBean(remotingDesc);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return isTccProxyTargetBean(remotingDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if it is proxy bean, check if the FactoryBean is Remoting bean
|
||||
*
|
||||
* @param bean the bean
|
||||
* @param beanName the bean name
|
||||
* @param applicationContext the application context
|
||||
* @return boolean boolean
|
||||
*/
|
||||
protected static boolean isRemotingFactoryBean(Object bean, String beanName,
|
||||
ApplicationContext applicationContext) {
|
||||
if (!SpringProxyUtils.isProxy(bean)) {
|
||||
return false;
|
||||
}
|
||||
//the FactoryBean of proxy bean
|
||||
String factoryBeanName = "&" + beanName;
|
||||
Object factoryBean = null;
|
||||
if (applicationContext != null && applicationContext.containsBean(factoryBeanName)) {
|
||||
factoryBean = applicationContext.getBean(factoryBeanName);
|
||||
}
|
||||
//not factory bean, needn't proxy
|
||||
if (factoryBean == null) {
|
||||
return false;
|
||||
}
|
||||
//get FactoryBean info
|
||||
return parserRemotingServiceInfo(factoryBean, 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* get remoting bean info: sofa:service, sofa:reference, dubbo:reference, dubbo:service
|
||||
*
|
||||
* @param bean the bean
|
||||
* @param beanName the bean name
|
||||
* @return if sofa:service, sofa:reference, dubbo:reference, dubbo:service return true, else return false
|
||||
*/
|
||||
protected static boolean parserRemotingServiceInfo(Object bean, String beanName) {
|
||||
RemotingParser remotingParser = DefaultRemotingParser.get().isRemoting(bean, beanName);
|
||||
if (remotingParser != null) {
|
||||
return DefaultRemotingParser.get().parserRemotingServiceInfo(bean, beanName, remotingParser) != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the remoting description of TCC bean
|
||||
*
|
||||
* @param beanName the bean name
|
||||
* @return remoting desc
|
||||
*/
|
||||
public static RemotingDesc getRemotingDesc(String beanName) {
|
||||
return DefaultRemotingParser.get().getRemotingBeanDesc(beanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a proxy bean for tcc service
|
||||
*
|
||||
* @param interfaceClass
|
||||
* @param fieldValue
|
||||
* @param actionInterceptor
|
||||
* @return
|
||||
*/
|
||||
public static <T> T createProxy(Class<T> interfaceClass, Object fieldValue, TccActionInterceptor actionInterceptor) {
|
||||
ProxyFactory factory = new ProxyFactory();
|
||||
factory.setTarget(fieldValue);
|
||||
factory.setInterfaces(interfaceClass);
|
||||
factory.addAdvice(actionInterceptor);
|
||||
|
||||
return (T) factory.getProxy();
|
||||
}
|
||||
}
|
||||
@@ -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.annotation;
|
||||
|
||||
/**
|
||||
* The interface Business.
|
||||
*/
|
||||
public interface Business {
|
||||
/**
|
||||
* Do biz string.
|
||||
*
|
||||
* @param msg the msg
|
||||
* @return the string
|
||||
*/
|
||||
String doBiz(String msg);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Business.
|
||||
*/
|
||||
public class BusinessImpl implements Business {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BusinessImpl.class);
|
||||
|
||||
@Override
|
||||
@GlobalTransactional(timeoutMills = 300000, name = "busi-doBiz")
|
||||
public String doBiz(String msg) {
|
||||
LOGGER.info("Business doBiz");
|
||||
return "hello " + msg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BusinessProxy implements InvocationHandler {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BusinessProxy.class);
|
||||
|
||||
private Object proxy;
|
||||
|
||||
public BusinessProxy(Object proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Before invoking proxy method.");
|
||||
}
|
||||
Object result = null;
|
||||
try {
|
||||
result = method.invoke(this.proxy, args);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to invoke method {}.", method.getName());
|
||||
}
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("After invoking proxy method.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.tcc.LocalTccAction;
|
||||
import io.seata.spring.tcc.LocalTccActionImpl;
|
||||
import io.seata.spring.tcc.TccAction;
|
||||
import io.seata.spring.tcc.TccActionImpl;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* GlobalTransactionScanner Unit Test
|
||||
*/
|
||||
public class GlobalTransactionScannerTest {
|
||||
/**
|
||||
* The Global transaction scanner.
|
||||
*/
|
||||
protected GlobalTransactionScanner globalTransactionScanner = new GlobalTransactionScanner("global-trans-scanner-test");
|
||||
|
||||
/**
|
||||
* Test wrap normal bean.
|
||||
*
|
||||
* @param bean the bean
|
||||
* @param beanName the bean name
|
||||
* @param cacheKey the cache key
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("normalBeanProvider")
|
||||
public void testWrapNormalBean(Object bean, String beanName, Object cacheKey) {
|
||||
Object result = globalTransactionScanner.wrapIfNecessary(bean, beanName, cacheKey);
|
||||
Assertions.assertNotSame(result, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrap nothing
|
||||
*
|
||||
* @param bean the bean
|
||||
* @param beanName the bean name
|
||||
* @param cacheKey the cache key
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("normalTccBeanProvider")
|
||||
public void testWrapNormalTccBean(Object bean, String beanName, Object cacheKey) {
|
||||
Object result = globalTransactionScanner.wrapIfNecessary(bean, beanName, cacheKey);
|
||||
Assertions.assertSame(result, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapped
|
||||
*
|
||||
* @param bean the bean
|
||||
* @param beanName the bean name
|
||||
* @param cacheKey the cache key
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("localTccBeanProvider")
|
||||
public void testWrapLocalTccBean(Object bean, String beanName, Object cacheKey) {
|
||||
TccAction result = (LocalTccAction) globalTransactionScanner.wrapIfNecessary(bean, beanName, cacheKey);
|
||||
Assertions.assertNotSame(result, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test after properties set.
|
||||
*/
|
||||
@Test
|
||||
public void testAfterPropertiesSet() {
|
||||
globalTransactionScanner.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal bean provider object [ ] [ ].
|
||||
*
|
||||
* @return the object [ ] [ ]
|
||||
*/
|
||||
static Stream<Arguments> normalBeanProvider() {
|
||||
Business business = new BusinessImpl();
|
||||
String beanName = "business";
|
||||
String cacheKey = "business-key";
|
||||
return Stream.of(
|
||||
Arguments.of(business, beanName, cacheKey)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal tcc bean provider object [ ] [ ].
|
||||
*
|
||||
* @return the object [ ] [ ]
|
||||
*/
|
||||
static Stream<Arguments> normalTccBeanProvider() {
|
||||
TccAction tccAction = new TccActionImpl();
|
||||
String beanName = "tccBean";
|
||||
String cacheKey = "tccBean-key";
|
||||
return Stream.of(
|
||||
Arguments.of(tccAction, beanName, cacheKey)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local tcc bean provider object [ ] [ ].
|
||||
*
|
||||
* @return the object [ ] [ ]
|
||||
*/
|
||||
static Stream<Arguments> localTccBeanProvider() {
|
||||
LocalTccAction localTccAction = new LocalTccActionImpl();
|
||||
String beanName = "lcoalTccBean";
|
||||
String cacheKey = "lcoalTccBean-key";
|
||||
return Stream.of(
|
||||
Arguments.of(localTccAction, beanName, cacheKey)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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 java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import io.seata.common.exception.FrameworkException;
|
||||
import io.seata.common.DefaultValues;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Wu
|
||||
*/
|
||||
public class MethodDescTest {
|
||||
|
||||
private static final GlobalTransactionScanner GLOBAL_TRANSACTION_SCANNER = new GlobalTransactionScanner(
|
||||
"global-trans-scanner-test");
|
||||
private static Method method = null;
|
||||
private static Class<?> targetClass = null;
|
||||
private static GlobalTransactional transactional = null;
|
||||
|
||||
public MethodDescTest() throws NoSuchMethodException {
|
||||
method = MockBusiness.class.getDeclaredMethod("doBiz", String.class);
|
||||
transactional = method.getAnnotation(GlobalTransactional.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAnnotation() throws NoSuchMethodException {
|
||||
GlobalTransactionalInterceptor globalTransactionalInterceptor = new GlobalTransactionalInterceptor(null);
|
||||
Method method = MockBusiness.class.getDeclaredMethod("doBiz", String.class);
|
||||
targetClass = Mockito.mock(MockBusiness.class).getClass();
|
||||
transactional = globalTransactionalInterceptor.getAnnotation(method, targetClass, GlobalTransactional.class);
|
||||
Assertions.assertEquals(transactional.timeoutMills(), 300000);
|
||||
method = null;
|
||||
transactional = globalTransactionalInterceptor.getAnnotation(method, targetClass, GlobalTransactional.class);
|
||||
Assertions.assertEquals(transactional.timeoutMills(), DefaultValues.DEFAULT_GLOBAL_TRANSACTION_TIMEOUT * 2);
|
||||
targetClass = null;
|
||||
transactional = globalTransactionalInterceptor.getAnnotation(method, targetClass, GlobalTransactional.class);
|
||||
Assertions.assertNull(transactional);
|
||||
// only class has Annotation, method is not null
|
||||
targetClass = Mockito.mock(MockMethodAnnotation.class).getClass();
|
||||
method = MockMethodAnnotation.class.getDeclaredMethod("doBiz", String.class);
|
||||
transactional = globalTransactionalInterceptor.getAnnotation(method, targetClass, GlobalTransactional.class);
|
||||
Assertions.assertEquals(transactional.name(), "doBiz");
|
||||
// only method has Annotation, class is not null
|
||||
targetClass = Mockito.mock(MockClassAnnotation.class).getClass();
|
||||
method = MockClassAnnotation.class.getDeclaredMethod("doBiz", String.class);
|
||||
transactional = globalTransactionalInterceptor.getAnnotation(method, targetClass, GlobalTransactional.class);
|
||||
Assertions.assertEquals(transactional.name(), "MockClassAnnotation");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGlobalTransactional() throws NoSuchMethodException {
|
||||
MockClassAnnotation mockClassAnnotation = new MockClassAnnotation();
|
||||
ProxyFactory proxyFactory = new ProxyFactory();
|
||||
proxyFactory.setTarget(mockClassAnnotation);
|
||||
proxyFactory.addAdvice(new GlobalTransactionalInterceptor(null));
|
||||
Object proxy = proxyFactory.getProxy();
|
||||
mockClassAnnotation = (MockClassAnnotation)proxy;
|
||||
mockClassAnnotation.toString();
|
||||
Assertions.assertNull(RootContext.getXID());
|
||||
mockClassAnnotation.hashCode();
|
||||
Assertions.assertNull(RootContext.getXID());
|
||||
mockClassAnnotation.equals("test");
|
||||
Assertions.assertNull(RootContext.getXID());
|
||||
try {
|
||||
mockClassAnnotation.doBiz("test");
|
||||
} catch (FrameworkException e) {
|
||||
Assertions.assertEquals("No available service", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTransactionAnnotation()
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
MethodDesc methodDesc = getMethodDesc();
|
||||
assertThat(methodDesc.getTransactionAnnotation()).isEqualTo(transactional);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
MethodDesc methodDesc = getMethodDesc();
|
||||
assertThat(methodDesc.getMethod()).isEqualTo(method);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetTransactionAnnotation()
|
||||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
MethodDesc methodDesc = getMethodDesc();
|
||||
assertThat(methodDesc.getTransactionAnnotation()).isNotNull();
|
||||
methodDesc.setTransactionAnnotation(null);
|
||||
assertThat(methodDesc.getTransactionAnnotation()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
MethodDesc methodDesc = getMethodDesc();
|
||||
assertThat(methodDesc.getMethod()).isNotNull();
|
||||
methodDesc.setMethod(null);
|
||||
assertThat(methodDesc.getMethod()).isNull();
|
||||
}
|
||||
|
||||
private MethodDesc getMethodDesc() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
//call the private method
|
||||
Method m = GlobalTransactionScanner.class.getDeclaredMethod("makeMethodDesc", GlobalTransactional.class,
|
||||
Method.class);
|
||||
m.setAccessible(true);
|
||||
return (MethodDesc)m.invoke(GLOBAL_TRANSACTION_SCANNER, transactional, method);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* the type mock business
|
||||
*/
|
||||
@GlobalTransactional(timeoutMills = DefaultValues.DEFAULT_GLOBAL_TRANSACTION_TIMEOUT * 2)
|
||||
private static class MockBusiness {
|
||||
@GlobalTransactional(timeoutMills = 300000, name = "busi-doBiz")
|
||||
public String doBiz(String msg) {
|
||||
return "hello " + msg;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the type mock class annotation
|
||||
*/
|
||||
@GlobalTransactional(name = "MockClassAnnotation")
|
||||
private static class MockClassAnnotation {
|
||||
public String doBiz(String msg) {
|
||||
return "hello " + msg;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the type mock method annotation
|
||||
*/
|
||||
private static class MockMethodAnnotation {
|
||||
@GlobalTransactional(name = "doBiz")
|
||||
public String doBiz(String msg) {
|
||||
return "hello " + msg;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
27
spring/src/test/java/io/seata/spring/tcc/LocalTccAction.java
Normal file
27
spring/src/test/java/io/seata/spring/tcc/LocalTccAction.java
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.tcc;
|
||||
|
||||
import io.seata.rm.tcc.api.LocalTCC;
|
||||
|
||||
/**
|
||||
* The interface Local tcc action.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
@LocalTCC
|
||||
public interface LocalTccAction extends TccAction {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.tcc;
|
||||
|
||||
/**
|
||||
* The type Local tcc action.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class LocalTccActionImpl extends TccActionImpl implements LocalTccAction {
|
||||
|
||||
}
|
||||
53
spring/src/test/java/io/seata/spring/tcc/TccAction.java
Normal file
53
spring/src/test/java/io/seata/spring/tcc/TccAction.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.tcc;
|
||||
|
||||
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
|
||||
|
||||
/**
|
||||
* The interface Tcc action.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public interface TccAction {
|
||||
|
||||
/**
|
||||
* Prepare boolean.
|
||||
*
|
||||
* @param actionContext the action context
|
||||
* @param i the
|
||||
* @return the boolean
|
||||
*/
|
||||
@TwoPhaseBusinessAction(name = "tccActionForSpringTest" , commitMethod = "commit", rollbackMethod = "rollback")
|
||||
boolean prepare(BusinessActionContext actionContext, int i);
|
||||
|
||||
/**
|
||||
* Commit boolean.
|
||||
*
|
||||
* @param actionContext the action context
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean commit(BusinessActionContext actionContext);
|
||||
|
||||
/**
|
||||
* Rollback boolean.
|
||||
*
|
||||
* @param actionContext the action context
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean rollback(BusinessActionContext actionContext);
|
||||
}
|
||||
41
spring/src/test/java/io/seata/spring/tcc/TccActionImpl.java
Normal file
41
spring/src/test/java/io/seata/spring/tcc/TccActionImpl.java
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.tcc;
|
||||
|
||||
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||
|
||||
/**
|
||||
* The type Tcc action.
|
||||
*
|
||||
* @author zhangsen
|
||||
*/
|
||||
public class TccActionImpl implements TccAction {
|
||||
|
||||
@Override
|
||||
public boolean prepare(BusinessActionContext actionContext, int i) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commit(BusinessActionContext actionContext) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rollback(BusinessActionContext actionContext) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user