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

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

82
spring/pom.xml Normal file
View 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>

View File

@@ -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;
}

View File

@@ -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();
}
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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";
}

View File

@@ -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};
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.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;
}
}

View File

@@ -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());
}
}
}

View File

@@ -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;
}
}

View 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]);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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)
);
}
}

View File

@@ -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;
}
}
}

View 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 {
}

View File

@@ -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 {
}

View 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);
}

View 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;
}
}