diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 64b45f46e30..240fcf7dec9 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -39,6 +39,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6184](https://github.com/apache/incubator-seata/pull/6184)] update NOTICE file - [[#6192](https://github.com/apache/incubator-seata/pull/6192)] remove the useless file - [[#6194](https://github.com/apache/incubator-seata/pull/6194)] fix asf.yaml parse error +- [[#5399](https://github.com/apache/incubator-seata/pull/5399)] optimizing branch register resource only at RM server end - [[#6154](https://github.com/apache/incubator-seata/pull/6154)] console log optimize for "kubectl logs -f" - [[#6116](https://github.com/apache/incubator-seata/pull/6116)] rewrite NettyPoolKey's hashcode and equals to fix duplicate construction of channel object pools - [[#6195](https://github.com/apache/incubator-seata/pull/6195)] update the url in change log to apache/incubator-seata @@ -78,10 +79,12 @@ Thanks to these contributors for their code commits. Please report an unintended - [lightClouds917](https://github.com/lightClouds917) - [xingfudeshi](https://github.com/xingfudeshi) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) +- [sunrui1225](https://github.com/sunrui1225) - [PeppaO](https://github.com/PeppaO) - [AlbumenJ](https://github.com/AlbumenJ) - [dreamskyvision](https://github.com/dreamskyvision) - [jsbxyyx](https://github.com/jsbxyyx) - [liuqiufeng](https://github.com/liuqiufeng) + Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 84fe561a2f7..55f930c8ffd 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -38,7 +38,7 @@ - [[#6184](https://github.com/apache/incubator-seata/pull/6184)] 更新NOTICE文件 - [[#6192](https://github.com/apache/incubator-seata/pull/6192)] 移除无用文件 - [[#6194](https://github.com/apache/incubator-seata/pull/6194)] 修复 asf.yaml 解析错误问题 -- [[#6116](https://github.com/apache/incubator-seata/pull/6116)] 重写NettyPoolKey的hashcode和equals,修复了channel对象池重复构建问题 +- [[#5399](https://github.com/apache/incubator-seata/pull/5399)] 分支注册只在RM端 - [[#6154](https://github.com/apache/incubator-seata/pull/6154)] 控制台日志优化 "kubectl logs -f" - [[#6116](https://github.com/apache/incubator-seata/pull/6116)] 重写NettyPoolKey的hashcode和equals,修复了channel对象池重复构建问题 - [[#6195](https://github.com/apache/incubator-seata/pull/6195)] 更新 change log 中的 seata url 为 apache/incubator-seata @@ -49,6 +49,7 @@ - [[#6004](https://github.com/apache/incubator-seata/pull/6004)] 优化RM,TM连接server快速失败 - [[#6243](https://github.com/apache/incubator-seata/pull/6243)] 优化控制台页眉中的链接 + ### security: - [[#6069](https://github.com/apache/incubator-seata/pull/6069)] 升级Guava依赖版本,修复安全漏洞 - [[#6144](https://github.com/apache/incubator-seata/pull/6144)] 升级Nacos依赖版本至1.4.6 @@ -78,10 +79,12 @@ - [lightClouds917](https://github.com/lightClouds917) - [xingfudeshi](https://github.com/xingfudeshi) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) +- [sunrui1225](https://github.com/sunrui1225) - [PeppaO](https://github.com/PeppaO) - [AlbumenJ](https://github.com/AlbumenJ) - [dreamskyvision](https://github.com/dreamskyvision) - [jsbxyyx](https://github.com/jsbxyyx) - [liuqiufeng](https://github.com/liuqiufeng) + 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/DefaultInterfaceParser.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/DefaultInterfaceParser.java index 89f8499db83..7474ef4a6d5 100644 --- a/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/DefaultInterfaceParser.java +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/DefaultInterfaceParser.java @@ -24,6 +24,7 @@ import java.util.List; /** + * @author leezongjie */ public class DefaultInterfaceParser implements InterfaceParser { @@ -63,4 +64,15 @@ public ProxyInvocationHandler parserInterfaceToProxy(Object target, String objec return null; } + @Override + public IfNeedEnhanceBean parseIfNeedEnhancement(Class beanClass) { + for (InterfaceParser interfaceParser : ALL_INTERFACE_PARSERS) { + IfNeedEnhanceBean ifNeedEnhanceBean = interfaceParser.parseIfNeedEnhancement(beanClass); + if (ifNeedEnhanceBean.isIfNeed()) { + return ifNeedEnhanceBean; + } + } + return new IfNeedEnhanceBean(); + } + } diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java index 5bb44043b8b..9b01e92a2f3 100644 --- a/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java @@ -22,6 +22,7 @@ import io.seata.common.ConfigurationKeys; import io.seata.common.util.CollectionUtils; +import io.seata.common.util.ReflectionUtil; import io.seata.config.ConfigurationCache; import io.seata.config.ConfigurationChangeListener; import io.seata.integration.tx.api.interceptor.handler.GlobalTransactionalInterceptorHandler; @@ -59,6 +60,19 @@ public ProxyInvocationHandler parserInterfaceToProxy(Object target, String objec return null; } + @Override + public IfNeedEnhanceBean parseIfNeedEnhancement(Class beanClass) { + Set> interfaceClasses = ReflectionUtil.getInterfaces(beanClass); + Class[] interfaceClasseArray = interfaceClasses.toArray(new Class[0]); + + IfNeedEnhanceBean ifNeedEnhanceBean = new IfNeedEnhanceBean(); + if (existsAnnotation(beanClass) || existsAnnotation(interfaceClasseArray)) { + ifNeedEnhanceBean.setIfNeed(true); + ifNeedEnhanceBean.setNeedEnhanceEnum(NeedEnhanceEnum.GLOBAL_TRANSACTIONAL_BEAN); + } + return ifNeedEnhanceBean; + } + private boolean existsAnnotation(Class... classes) { boolean result = false; if (CollectionUtils.isNotEmpty(classes)) { diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/IfNeedEnhanceBean.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/IfNeedEnhanceBean.java new file mode 100644 index 00000000000..c0d1e91737b --- /dev/null +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/IfNeedEnhanceBean.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.integration.tx.api.interceptor.parser; + +public class IfNeedEnhanceBean { + + private boolean ifNeed; + + private NeedEnhanceEnum needEnhanceEnum; + + public boolean isIfNeed() { + return ifNeed; + } + + public void setIfNeed(boolean ifNeed) { + this.ifNeed = ifNeed; + } + + public NeedEnhanceEnum getNeedEnhanceEnum() { + return needEnhanceEnum; + } + + public void setNeedEnhanceEnum(NeedEnhanceEnum needEnhanceEnum) { + this.needEnhanceEnum = needEnhanceEnum; + } + +} diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/InterfaceParser.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/InterfaceParser.java index 23db3c4a2e5..2584c6459e4 100644 --- a/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/InterfaceParser.java +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/InterfaceParser.java @@ -24,5 +24,7 @@ public interface InterfaceParser { ProxyInvocationHandler parserInterfaceToProxy(Object target, String objectName) throws Exception; + IfNeedEnhanceBean parseIfNeedEnhancement(Class beanClass); + } diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/NeedEnhanceEnum.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/NeedEnhanceEnum.java new file mode 100644 index 00000000000..7ac30889c79 --- /dev/null +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/interceptor/parser/NeedEnhanceEnum.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.integration.tx.api.interceptor.parser; + + +public enum NeedEnhanceEnum { + + /** + * global transactional bean type + */ + GLOBAL_TRANSACTIONAL_BEAN, + /** + * service bean type + */ + SERVICE_BEAN; +} diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/RemotingParser.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/RemotingParser.java index 569f5749adb..71664b0542f 100644 --- a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/RemotingParser.java +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/RemotingParser.java @@ -54,6 +54,15 @@ public interface RemotingParser { */ boolean isService(Object bean, String beanName) throws FrameworkException; + /** + * if it is service bean ? + * + * @param beanClass the bean class + * @return boolean boolean + * @throws FrameworkException the framework exception + */ + boolean isService(Class beanClass) throws FrameworkException; + /** * get the remoting bean info * diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/DefaultRemotingParser.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/DefaultRemotingParser.java index f3f8eafab42..e1ca71b6220 100644 --- a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/DefaultRemotingParser.java +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/DefaultRemotingParser.java @@ -132,6 +132,21 @@ public boolean isService(Object bean, String beanName) { return false; } + /** + * is service bean ? + * + * @param beanClass the bean class + * @return boolean boolean + */ + public boolean isService(Class beanClass) { + for (RemotingParser remotingParser : allRemotingParsers) { + if (remotingParser.isService(beanClass)) { + return true; + } + } + return false; + } + /** * get the remoting Service desc * diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/DubboRemotingParser.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/DubboRemotingParser.java index 681e096ad90..4014cd5e8c1 100644 --- a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/DubboRemotingParser.java +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/DubboRemotingParser.java @@ -41,6 +41,12 @@ public boolean isService(Object bean, String beanName) throws FrameworkException || "org.apache.dubbo.config.spring.ServiceBean".equals(c.getName()); } + @Override + public boolean isService(Class beanClass) throws FrameworkException { + return "com.alibaba.dubbo.config.spring.ServiceBean".equals(beanClass.getName()) + || "org.apache.dubbo.config.spring.ServiceBean".equals(beanClass.getName()); + } + @Override public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException { if (!this.isRemoting(bean, beanName)) { diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/HSFRemotingParser.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/HSFRemotingParser.java index aa76a403539..4818f127d43 100644 --- a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/HSFRemotingParser.java +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/HSFRemotingParser.java @@ -64,6 +64,12 @@ public boolean isService(Object bean, String beanName) { return isHsf && "com.taobao.hsf.app.spring.util.HSFSpringProviderBean".equals(beanClassName); } + @Override + public boolean isService(Class beanClass) throws FrameworkException { + String beanClassName = beanClass.getName(); + return isHsf && "com.taobao.hsf.app.spring.util.HSFSpringProviderBean".equals(beanClassName); + } + @Override public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException { if (!this.isRemoting(bean, beanName)) { diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/SofaRpcRemotingParser.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/SofaRpcRemotingParser.java index a5872ba133b..4cb03f709d8 100644 --- a/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/SofaRpcRemotingParser.java +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/remoting/parser/SofaRpcRemotingParser.java @@ -42,6 +42,12 @@ public boolean isService(Object bean, String beanName) throws FrameworkException return "com.alipay.sofa.runtime.spring.factory.ServiceFactoryBean".equals(beanClassName); } + @Override + public boolean isService(Class beanClass) throws FrameworkException { + String beanClassName = beanClass.getName(); + return "com.alipay.sofa.runtime.spring.factory.ServiceFactoryBean".equals(beanClassName); + } + @Override public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException { if (!this.isRemoting(bean, beanName)) { diff --git a/integration-tx-api/src/main/java/io/seata/integration/tx/api/util/ProxyUtil.java b/integration-tx-api/src/main/java/io/seata/integration/tx/api/util/ProxyUtil.java index 85888033845..9efbf8b0f0d 100644 --- a/integration-tx-api/src/main/java/io/seata/integration/tx/api/util/ProxyUtil.java +++ b/integration-tx-api/src/main/java/io/seata/integration/tx/api/util/ProxyUtil.java @@ -37,6 +37,21 @@ public static T createProxy(T target) { return createProxy(target, target.getClass().getName()); } + /** + * The API for generating proxy for target. It can be used by spring aop, or + * provide user to generate proxy manually. + *

+ * At TM side, It can be used for the target bean with @GlobalTransactional or @GlobalLock to generate proxy which start global transaction. + * At RM side, if you use TCC mode, It can be for target bean with @TwoPhaseBusinessAction to generate proxy which register branch source + * and branch transaction. If you want to use this API to generate proxy manual like dubbo, you must make sure the target bean is the + * business bean with @GlobalTransactional annotation. If you pass the ServiceBean(com.alibaba.dubbo.config.spring.ServiceBean) or + * ReferenceBean(com.alibaba.dubbo.config.spring.ReferenceBean), it will don't work. + * + * @param target the business bean + * @param beanName the business bean name + * @return the proxy bean + * @param the generics class + */ public static T createProxy(T target, String beanName) { try { synchronized (PROXYED_SET) { diff --git a/spring/src/main/java/io/seata/rm/fence/SpringFenceHandler.java b/spring/src/main/java/io/seata/rm/fence/SpringFenceHandler.java index 1fd56e79927..e6576054328 100644 --- a/spring/src/main/java/io/seata/rm/fence/SpringFenceHandler.java +++ b/spring/src/main/java/io/seata/rm/fence/SpringFenceHandler.java @@ -163,7 +163,9 @@ public boolean commitFence(Method commitMethod, Object targetTCCBean, } return false; } - return updateStatusAndInvokeTargetMethod(conn, commitMethod, targetTCCBean, xid, branchId, CommonFenceConstant.STATUS_COMMITTED, status, args); + boolean result = updateStatusAndInvokeTargetMethod(conn, commitMethod, targetTCCBean, xid, branchId, CommonFenceConstant.STATUS_COMMITTED, status, args); + LOGGER.info("Common fence commit result: {}. xid: {}, branchId: {}", result, xid, branchId); + return result; } catch (Throwable t) { status.setRollbackOnly(); throw new SkipCallbackWrapperException(t); @@ -210,7 +212,9 @@ public boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, return false; } } - return updateStatusAndInvokeTargetMethod(conn, rollbackMethod, targetTCCBean, xid, branchId, CommonFenceConstant.STATUS_ROLLBACKED, status, args); + boolean result = updateStatusAndInvokeTargetMethod(conn, rollbackMethod, targetTCCBean, xid, branchId, CommonFenceConstant.STATUS_ROLLBACKED, status, args); + LOGGER.info("Common fence rollback result: {}. xid: {}, branchId: {}", result, xid, branchId); + return result; } catch (Throwable t) { status.setRollbackOnly(); throw new SkipCallbackWrapperException(t); @@ -237,13 +241,13 @@ private static boolean insertCommonFenceLog(Connection conn, String xid, Long br } /** - * Update TCC Fence status and invoke target method + * Update Common Fence status and invoke target method * * @param method target method * @param targetTCCBean target bean * @param xid the global transaction id * @param branchId the branch transaction id - * @param status the tcc fence status + * @param status the common fence status * @return the boolean */ private static boolean updateStatusAndInvokeTargetMethod(Connection conn, Method method, Object targetTCCBean, diff --git a/spring/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java b/spring/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java index f37b6117be3..916d3dd1aa6 100644 --- a/spring/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java +++ b/spring/src/main/java/io/seata/spring/annotation/GlobalTransactionScanner.java @@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; +import com.google.common.collect.ImmutableSet; import io.seata.common.util.CollectionUtils; import io.seata.common.util.StringUtils; import io.seata.config.ConfigurationCache; @@ -42,6 +43,8 @@ import io.seata.integration.tx.api.interceptor.handler.GlobalTransactionalInterceptorHandler; import io.seata.integration.tx.api.interceptor.handler.ProxyInvocationHandler; import io.seata.integration.tx.api.interceptor.parser.DefaultInterfaceParser; +import io.seata.integration.tx.api.interceptor.parser.IfNeedEnhanceBean; +import io.seata.integration.tx.api.interceptor.parser.NeedEnhanceEnum; import io.seata.integration.tx.api.remoting.parser.DefaultRemotingParser; import io.seata.rm.RMClient; import io.seata.spring.annotation.scannercheckers.PackageScannerChecker; @@ -62,10 +65,13 @@ import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; @@ -114,6 +120,8 @@ public class GlobalTransactionScanner extends AbstractAutoProxyCreator private ApplicationContext applicationContext; + private static final Set NEED_ENHANCE_BEAN_NAME_SET = new HashSet<>(); + /** * Instantiates a new Global transaction scanner. @@ -280,6 +288,9 @@ protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) if (PROXYED_SET.contains(beanName)) { return bean; } + if (!NEED_ENHANCE_BEAN_NAME_SET.contains(beanName)) { + return bean; + } interceptor = null; ProxyInvocationHandler proxyInvocationHandler = DefaultInterfaceParser.get().parserInterfaceToProxy(bean, beanName); if (proxyInvocationHandler == null) { @@ -469,8 +480,71 @@ public void afterPropertiesSet() { if (initialized.compareAndSet(false, true)) { initClient(); } + + this.findBusinessBeanNamesNeededEnhancement(); } + private void findBusinessBeanNamesNeededEnhancement() { + if (applicationContext instanceof ConfigurableApplicationContext) { + ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext; + ConfigurableListableBeanFactory configurableListableBeanFactory = configurableApplicationContext.getBeanFactory(); + + String[] beanNames = applicationContext.getBeanDefinitionNames(); + for (String contextBeanName : beanNames) { + BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(contextBeanName); + if (StringUtils.isBlank(beanDefinition.getBeanClassName())) { + continue; + } + if (IGNORE_ENHANCE_CHECK_SET.contains(beanDefinition.getBeanClassName())) { + continue; + } + try { + // get the class by bean definition class name + Class beanClass = Class.forName(beanDefinition.getBeanClassName()); + // check if it needs enhancement by the class + IfNeedEnhanceBean ifNeedEnhanceBean = DefaultInterfaceParser.get().parseIfNeedEnhancement(beanClass); + if (!ifNeedEnhanceBean.isIfNeed()) { + continue; + } + if (ifNeedEnhanceBean.getNeedEnhanceEnum().equals(NeedEnhanceEnum.SERVICE_BEAN)) { + // the native bean which dubbo, sofa bean service bean referenced + PropertyValue propertyValue = beanDefinition.getPropertyValues().getPropertyValue("ref"); + if (propertyValue == null) { + // the native bean which HSF service bean referenced + propertyValue = beanDefinition.getPropertyValues().getPropertyValue("target"); + } + if (propertyValue != null) { + RuntimeBeanReference r = (RuntimeBeanReference) propertyValue.getValue(); + if (r != null && StringUtils.isNotBlank(r.getBeanName())) { + NEED_ENHANCE_BEAN_NAME_SET.add(r.getBeanName()); + continue; + } + } + // the native bean which local tcc service bean referenced + NEED_ENHANCE_BEAN_NAME_SET.add(contextBeanName); + } else if (ifNeedEnhanceBean.getNeedEnhanceEnum().equals(NeedEnhanceEnum.GLOBAL_TRANSACTIONAL_BEAN)) { + // global transactional bean + NEED_ENHANCE_BEAN_NAME_SET.add(contextBeanName); + } + } catch (ClassNotFoundException e) { + LOGGER.warn("check if need enhance bean error, it can be ignore", e); + } + } + LOGGER.info("The needed enhancement business beans are : {}", NEED_ENHANCE_BEAN_NAME_SET); + } + } + + private static final Set IGNORE_ENHANCE_CHECK_SET = ImmutableSet.of( + "io.seata.spring.annotation.GlobalTransactionScanner" + , "io.seata.rm.fence.SpringFenceConfig" + , "org.springframework.context.annotation.internalConfigurationAnnotationProcessor" + , "org.springframework.context.annotation.internalAutowiredAnnotationProcessor" + , "org.springframework.context.annotation.internalCommonAnnotationProcessor" + , "org.springframework.context.event.internalEventListenerProcessor" + , "org.springframework.context.event.internalEventListenerFactory" + ); + + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; diff --git a/spring/src/main/java/io/seata/spring/remoting/parser/RemotingFactoryBeanParser.java b/spring/src/main/java/io/seata/spring/remoting/parser/RemotingFactoryBeanParser.java index a5635fbbbf9..052061e1c30 100644 --- a/spring/src/main/java/io/seata/spring/remoting/parser/RemotingFactoryBeanParser.java +++ b/spring/src/main/java/io/seata/spring/remoting/parser/RemotingFactoryBeanParser.java @@ -73,6 +73,11 @@ public boolean isService(Object bean, String beanName) { return DefaultRemotingParser.get().isService(factoryBean, getFactoryBeanName(beanName)); } + @Override + public boolean isService(Class beanClass) throws FrameworkException { + return false; + } + @Override public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException { Object factoryBean = getRemotingFactoryBean(bean, beanName); diff --git a/tcc/src/main/java/io/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java b/tcc/src/main/java/io/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java index bbdffe0c7e8..db611a80523 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java +++ b/tcc/src/main/java/io/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java @@ -35,7 +35,6 @@ import io.seata.integration.tx.api.interceptor.SeataInterceptorPosition; import io.seata.integration.tx.api.interceptor.TwoPhaseBusinessActionParam; import io.seata.integration.tx.api.interceptor.handler.AbstractProxyInvocationHandler; -import io.seata.integration.tx.api.remoting.RemotingDesc; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; import org.slf4j.MDC; @@ -52,12 +51,12 @@ public class TccActionInterceptorHandler extends AbstractProxyInvocationHandler private ActionInterceptorHandler actionInterceptorHandler = new ActionInterceptorHandler(); private Set methodsToProxy; - private RemotingDesc remotingDesc; + private Object targetBean; private Map parseAnnotationCache = new ConcurrentHashMap<>(); - public TccActionInterceptorHandler(RemotingDesc remotingDesc, Set methodsToProxy) { - this.remotingDesc = remotingDesc; + public TccActionInterceptorHandler(Object targetBean, Set methodsToProxy) { + this.targetBean = targetBean; this.methodsToProxy = methodsToProxy; } @@ -113,8 +112,8 @@ protected Object doInvoke(InvocationWrapper invocation) throws Throwable { private TwoPhaseBusinessAction parseAnnotation(Method methodKey) throws NoSuchMethodException { TwoPhaseBusinessAction result = parseAnnotationCache.computeIfAbsent(methodKey, method -> { TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class); - if (businessAction == null && remotingDesc.getServiceClass() != null) { - Set> interfaceClasses = ReflectionUtil.getInterfaces(remotingDesc.getServiceClass()); + if (businessAction == null && targetBean.getClass() != null) { + Set> interfaceClasses = ReflectionUtil.getInterfaces(targetBean.getClass()); if (interfaceClasses != null) { for (Class interClass : interfaceClasses) { try { diff --git a/tcc/src/main/java/io/seata/rm/tcc/interceptor/parser/TccActionInterceptorParser.java b/tcc/src/main/java/io/seata/rm/tcc/interceptor/parser/TccActionInterceptorParser.java index 6496df4a877..d28b6d3e2d3 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/interceptor/parser/TccActionInterceptorParser.java +++ b/tcc/src/main/java/io/seata/rm/tcc/interceptor/parser/TccActionInterceptorParser.java @@ -16,61 +16,63 @@ */ package io.seata.rm.tcc.interceptor.parser; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + import io.seata.common.util.ReflectionUtil; -import io.seata.integration.tx.api.interceptor.TxBeanParserUtils; import io.seata.integration.tx.api.interceptor.handler.ProxyInvocationHandler; import io.seata.integration.tx.api.interceptor.parser.DefaultResourceRegisterParser; +import io.seata.integration.tx.api.interceptor.parser.IfNeedEnhanceBean; import io.seata.integration.tx.api.interceptor.parser.InterfaceParser; -import io.seata.integration.tx.api.remoting.RemotingDesc; +import io.seata.integration.tx.api.interceptor.parser.NeedEnhanceEnum; import io.seata.integration.tx.api.remoting.parser.DefaultRemotingParser; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; import io.seata.rm.tcc.interceptor.TccActionInterceptorHandler; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** */ public class TccActionInterceptorParser implements InterfaceParser { + private static final Logger LOGGER = LoggerFactory.getLogger(TccActionInterceptorParser.class); + @Override public ProxyInvocationHandler parserInterfaceToProxy(Object target, String objectName) { - boolean isTxRemotingBean = TxBeanParserUtils.isTxRemotingBean(target, objectName); - if (isTxRemotingBean) { - RemotingDesc remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(target); - if (remotingDesc != null) { - if (remotingDesc.isService()) { - DefaultResourceRegisterParser.get().registerResource(target, objectName); - } - if (remotingDesc.isReference()) { - //if it is a tcc remote reference - Set methodsToProxy = tccProxyTargetMethod(remotingDesc); - if (remotingDesc != null && !methodsToProxy.isEmpty()) { - ProxyInvocationHandler proxyInvocationHandler = new TccActionInterceptorHandler(remotingDesc, methodsToProxy); - return proxyInvocationHandler; - } - } - } + // eliminate the bean without two phase annotation. + Set methodsToProxy = this.tccProxyTargetMethod(target); + if (methodsToProxy.isEmpty()) { + return null; } - return null; + // register resource and enhance with interceptor + DefaultResourceRegisterParser.get().registerResource(target, objectName); + return new TccActionInterceptorHandler(target, methodsToProxy); + } + + @Override + public IfNeedEnhanceBean parseIfNeedEnhancement(Class beanClass) { + IfNeedEnhanceBean ifNeedEnhanceBean = new IfNeedEnhanceBean(); + if (DefaultRemotingParser.get().isService(beanClass)) { + ifNeedEnhanceBean.setIfNeed(true); + ifNeedEnhanceBean.setNeedEnhanceEnum(NeedEnhanceEnum.SERVICE_BEAN); + } + return ifNeedEnhanceBean; } /** * is TCC proxy-bean/target-bean: LocalTCC , the proxy bean of sofa:reference/dubbo:reference * - * @param remotingDesc the remoting desc + * @param target the remoting desc * @return boolean boolean */ - private Set tccProxyTargetMethod(RemotingDesc remotingDesc) { - if (!remotingDesc.isReference() || remotingDesc == null) { - return Collections.emptySet(); - } + + private Set tccProxyTargetMethod(Object target) { Set methodsToProxy = new HashSet<>(); //check if it is TCC bean - Class tccServiceClazz = remotingDesc.getServiceClass(); + Class tccServiceClazz = target.getClass(); Set methods = new HashSet<>(Arrays.asList(tccServiceClazz.getMethods())); Set> interfaceClasses = ReflectionUtil.getInterfaces(tccServiceClazz); if (interfaceClasses != null) { diff --git a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParser.java b/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParser.java index 07a8b9214f7..f786924ffad 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParser.java +++ b/tcc/src/main/java/io/seata/rm/tcc/remoting/parser/LocalTCCRemotingParser.java @@ -42,6 +42,11 @@ public boolean isService(Object bean, String beanName) { return isLocalTCC(bean); } + @Override + public boolean isService(Class beanClass) throws FrameworkException { + return isLocalTCC(beanClass); + } + @Override public RemotingDesc getServiceDesc(Object bean, String beanName) throws FrameworkException { if (!this.isRemoting(bean, beanName)) { @@ -84,6 +89,10 @@ public short getProtocol() { */ private boolean isLocalTCC(Object bean) { Class classType = bean.getClass(); + return isLocalTCC(classType); + } + + private boolean isLocalTCC(Class classType) { Set> interfaceClasses = ReflectionUtil.getInterfaces(classType); for (Class interClass : interfaceClasses) { if (interClass.isAnnotationPresent(LocalTCC.class)) { diff --git a/tcc/src/main/java/io/seata/rm/tcc/resource/parser/TccRegisterResourceParser.java b/tcc/src/main/java/io/seata/rm/tcc/resource/parser/TccRegisterResourceParser.java index 1918dd24195..c3a40357151 100644 --- a/tcc/src/main/java/io/seata/rm/tcc/resource/parser/TccRegisterResourceParser.java +++ b/tcc/src/main/java/io/seata/rm/tcc/resource/parser/TccRegisterResourceParser.java @@ -16,80 +16,68 @@ */ package io.seata.rm.tcc.resource.parser; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + import io.seata.common.exception.FrameworkException; import io.seata.common.util.ReflectionUtil; import io.seata.common.util.StringUtils; import io.seata.integration.tx.api.interceptor.ActionContextUtil; -import io.seata.integration.tx.api.interceptor.TxBeanParserUtils; import io.seata.integration.tx.api.interceptor.parser.RegisterResourceParser; -import io.seata.integration.tx.api.remoting.RemotingDesc; -import io.seata.integration.tx.api.remoting.parser.DefaultRemotingParser; import io.seata.rm.DefaultResourceManager; import io.seata.rm.tcc.TCCResource; import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - /** */ public class TccRegisterResourceParser implements RegisterResourceParser { @Override public void registerResource(Object target, String beanName) { - boolean isTxRemotingBean = TxBeanParserUtils.isTxRemotingBean(target, beanName); - if (isTxRemotingBean) { - RemotingDesc remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(target); - if (remotingDesc != null) { - if (remotingDesc.isService()) { - try { - //service bean, registry resource - Class serviceClass = remotingDesc.getServiceClass(); - Set methods = new HashSet<>(Arrays.asList(serviceClass.getMethods())); - Set> interfaceClasses = ReflectionUtil.getInterfaces(serviceClass); - if (interfaceClasses != null) { - for (Class interClass : interfaceClasses) { - methods.addAll(Arrays.asList(interClass.getMethods())); - } - } - Object targetBean = remotingDesc.getTargetBean(); - for (Method m : methods) { - TwoPhaseBusinessAction twoPhaseBusinessAction = m.getAnnotation(TwoPhaseBusinessAction.class); - if (twoPhaseBusinessAction != null) { - TCCResource tccResource = new TCCResource(); - if (StringUtils.isBlank(twoPhaseBusinessAction.name())) { - throw new FrameworkException("TCC bean name cannot be null or empty"); - } - tccResource.setActionName(twoPhaseBusinessAction.name()); - tccResource.setTargetBean(targetBean); - tccResource.setPrepareMethod(m); - tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod()); - tccResource.setCommitMethod(serviceClass.getMethod(twoPhaseBusinessAction.commitMethod(), - twoPhaseBusinessAction.commitArgsClasses())); - tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod()); - tccResource.setRollbackMethod(serviceClass.getMethod(twoPhaseBusinessAction.rollbackMethod(), - twoPhaseBusinessAction.rollbackArgsClasses())); - // set argsClasses - tccResource.setCommitArgsClasses(twoPhaseBusinessAction.commitArgsClasses()); - tccResource.setRollbackArgsClasses(twoPhaseBusinessAction.rollbackArgsClasses()); - // set phase two method's keys - tccResource.setPhaseTwoCommitKeys(this.getTwoPhaseArgs(tccResource.getCommitMethod(), - twoPhaseBusinessAction.commitArgsClasses())); - tccResource.setPhaseTwoRollbackKeys(this.getTwoPhaseArgs(tccResource.getRollbackMethod(), - twoPhaseBusinessAction.rollbackArgsClasses())); - //registry tcc resource - DefaultResourceManager.get().registerResource(tccResource); - } - } - } catch (Throwable t) { - throw new FrameworkException(t, "parser remoting service error"); - } + try { + //service bean, registry resource + Class serviceClass = target.getClass(); + this.executeRegisterResource(target, new HashSet<>(Arrays.asList(serviceClass.getMethods())), target.getClass()); + Set> interfaceClasses = ReflectionUtil.getInterfaces(serviceClass); + for (Class interClass : interfaceClasses) { + this.executeRegisterResource(target, new HashSet<>(Arrays.asList(interClass.getMethods())), interClass); + } + } catch (Throwable t) { + throw new FrameworkException(t, "parser remoting service error"); + } + } + + private void executeRegisterResource(Object target, Set methods, Class targetServiceClass) throws NoSuchMethodException { + for (Method m : methods) { + TwoPhaseBusinessAction twoPhaseBusinessAction = m.getAnnotation(TwoPhaseBusinessAction.class); + if (twoPhaseBusinessAction != null) { + TCCResource tccResource = new TCCResource(); + if (StringUtils.isBlank(twoPhaseBusinessAction.name())) { + throw new FrameworkException("TCC bean name cannot be null or empty"); } + tccResource.setActionName(twoPhaseBusinessAction.name()); + tccResource.setTargetBean(target); + tccResource.setPrepareMethod(m); + tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod()); + tccResource.setCommitMethod(targetServiceClass.getMethod(twoPhaseBusinessAction.commitMethod(), + twoPhaseBusinessAction.commitArgsClasses())); + tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod()); + tccResource.setRollbackMethod(targetServiceClass.getMethod(twoPhaseBusinessAction.rollbackMethod(), + twoPhaseBusinessAction.rollbackArgsClasses())); + // set argsClasses + tccResource.setCommitArgsClasses(twoPhaseBusinessAction.commitArgsClasses()); + tccResource.setRollbackArgsClasses(twoPhaseBusinessAction.rollbackArgsClasses()); + // set phase two method's keys + tccResource.setPhaseTwoCommitKeys(this.getTwoPhaseArgs(tccResource.getCommitMethod(), + twoPhaseBusinessAction.commitArgsClasses())); + tccResource.setPhaseTwoRollbackKeys(this.getTwoPhaseArgs(tccResource.getRollbackMethod(), + twoPhaseBusinessAction.rollbackArgsClasses())); + //registry tcc resource + DefaultResourceManager.get().registerResource(tccResource); } } }