diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 2d681fcd548..33cc0baaeb8 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -36,10 +36,12 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6385](https://github.com/apache/incubator-seata/pull/6385)] fix the bug where Role.participant does not execute hooks but clears them. - [[#6465](https://github.com/apache/incubator-seata/pull/6465)] fix(6257): fix saga mode replay context lost start in 2.x - [[#6469](https://github.com/apache/incubator-seata/pull/6469)] fix Error in insert sql of [lock_table] data table to sqlserver database +- [[#6475](https://github.com/apache/incubator-seata/pull/6475)] fix the failure of @BusinessActionContextParamete annotation to set parameters into io.seata.rm.tcc.api.BusinessActionContext at TCC mode. - [[#6492](https://github.com/apache/incubator-seata/pull/6492)] fix XA did not rollback but close when executing a long-running SQL(or deadlock SQL) - [[#6497](https://github.com/apache/incubator-seata/pull/6497)] fix tcc properties class when autoconfigure + ### optimize: - [[#6031](https://github.com/apache/incubator-seata/pull/6031)] add a check for the existence of the undolog table - [[#6089](https://github.com/apache/incubator-seata/pull/6089)] modify the semantics of RaftServerFactory and remove unnecessary singleton @@ -191,8 +193,10 @@ Thanks to these contributors for their code commits. Please report an unintended - [yixia](https://github.com/wt-better) - [MikhailNavitski](https://github.com/MikhailNavitski) - [deung](https://github.com/deung) +- [TakeActionNow2019](https://github.com/TakeActionNow2019) - [tanyaofei](https://github.com/tanyaofei) - [xjlgod](https://github.com/xjlgod) + 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 23091b29a06..820ff01f89c 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -36,10 +36,12 @@ - [[#6385](https://github.com/apache/incubator-seata/pull/6385)] 修复Role.Participant不执行hook但会清理的问题 - [[#6465](https://github.com/apache/incubator-seata/pull/6465)] 修复2.0下saga模式的context replay丢失start问题 - [[#6469](https://github.com/apache/incubator-seata/pull/6469)] 修复在sqlserver数据库下[lock_table]数据表的插入操作sql中存在的错误 +- [[#6475](https://github.com/apache/incubator-seata/pull/6475)] 修复TCC模式@BusinessActionContextParamete注解无法将参数设置到BusinessActionContext的问题 - [[#6492](https://github.com/apache/incubator-seata/pull/6492)] 修复XA执行长时间SQL(或死锁SQL)没有完成回滚就释放连接 - [[#6497](https://github.com/apache/incubator-seata/pull/6497)] 修复自动装配时的seata tcc 配置类 + ### optimize: - [[#6031](https://github.com/apache/incubator-seata/pull/6031)] 添加undo_log表的存在性校验 - [[#6089](https://github.com/apache/incubator-seata/pull/6089)] 修改RaftServerFactory语义并删除不必要的单例构建 @@ -188,8 +190,10 @@ - [yixia](https://github.com/wt-better) - [MikhailNavitski](https://github.com/MikhailNavitski) - [deung](https://github.com/deung) +- [TakeActionNow2019](https://github.com/TakeActionNow2019) - [tanyaofei](https://github.com/tanyaofei) - [xjlgod](https://github.com/xjlgod) + 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/spring/src/test/java/org/apache/seata/spring/tcc/TccActionInterceptorHandlerTest.java b/spring/src/test/java/org/apache/seata/spring/tcc/TccActionInterceptorHandlerTest.java new file mode 100644 index 00000000000..b4f18f20061 --- /dev/null +++ b/spring/src/test/java/org/apache/seata/spring/tcc/TccActionInterceptorHandlerTest.java @@ -0,0 +1,75 @@ +/* + * 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 org.apache.seata.spring.tcc; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.seata.integration.tx.api.interceptor.InvocationWrapper; +import org.apache.seata.rm.tcc.api.BusinessActionContext; +import org.apache.seata.rm.tcc.api.TwoPhaseBusinessAction; +import org.apache.seata.rm.tcc.interceptor.TccActionInterceptorHandler; +import org.apache.seata.spring.annotation.AdapterInvocationWrapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; +import java.util.HashSet; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +class TccActionInterceptorHandlerTest { + + protected TccActionInterceptorHandler tccActionInterceptorHandler = new TccActionInterceptorHandler( + null, + new HashSet() {{ + add("prepare"); + }} + ); + + /** + * Test method "parseAnnotation" of TccActionInterceptorHandler + * + * @throws Throwable + */ + @Test + void testParseAnnotation() throws Throwable { + // mock MethodInvocation + NormalTccActionImpl tccAction = new NormalTccActionImpl(); + Method classMethod = NormalTccActionImpl.class.getMethod("prepare", BusinessActionContext.class); + MethodInvocation mockInvocation = mock(MethodInvocation.class); + when(mockInvocation.getMethod()).thenReturn(classMethod); + when(mockInvocation.getArguments()).thenReturn(new Object[]{new BusinessActionContext()}); + when(mockInvocation.proceed()).thenAnswer(invocation -> classMethod.invoke(tccAction, mockInvocation.getArguments())); + + // mock AdapterInvocationWrapper + AdapterInvocationWrapper invocationWrapper = new AdapterInvocationWrapper(mockInvocation); + when(invocationWrapper.getTarget()).thenReturn(tccAction); + + // invoke private method "parseAnnotation" of TccActionInterceptorHandler + Method method = TccActionInterceptorHandler.class.getDeclaredMethod("parseAnnotation", InvocationWrapper.class); + method.setAccessible(true); + Object[] results = (Object[]) method.invoke(tccActionInterceptorHandler, invocationWrapper); + System.out.println(results); + + // test results + Method interfaceMethod = NormalTccAction.class.getMethod("prepare", BusinessActionContext.class); + Assertions.assertEquals(interfaceMethod, results[0]); + Assertions.assertEquals(true, results[1] instanceof TwoPhaseBusinessAction); + + } +} diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java index acf84663c1d..e79515a38f6 100644 --- a/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java +++ b/tcc/src/main/java/org/apache/seata/rm/tcc/interceptor/TccActionInterceptorHandler.java @@ -38,6 +38,8 @@ import org.apache.seata.integration.tx.api.interceptor.TwoPhaseBusinessActionParam; import org.apache.seata.integration.tx.api.interceptor.handler.AbstractProxyInvocationHandler; import org.apache.seata.rm.tcc.api.TwoPhaseBusinessAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.slf4j.MDC; @@ -46,6 +48,8 @@ public class TccActionInterceptorHandler extends AbstractProxyInvocationHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(TccActionInterceptorHandler.class); + private static final int ORDER_NUM = ConfigurationFactory.getInstance().getInt(TCC_ACTION_INTERCEPTOR_ORDER, DefaultValues.TCC_ACTION_INTERCEPTOR_ORDER); @@ -54,7 +58,7 @@ public class TccActionInterceptorHandler extends AbstractProxyInvocationHandler private Set methodsToProxy; protected Object targetBean; - protected Map parseAnnotationCache = new ConcurrentHashMap<>(); + protected Map parseAnnotationCache = new ConcurrentHashMap<>(); public TccActionInterceptorHandler(Object targetBean, Set methodsToProxy) { this.targetBean = targetBean; @@ -67,8 +71,10 @@ protected Object doInvoke(InvocationWrapper invocation) throws Throwable { //not in transaction, or this interceptor is disabled return invocation.proceed(); } - Method method = invocation.getMethod(); - Annotation businessAction = parseAnnotation(method); + + Object[] methodAndAnnotation = parseAnnotation(invocation); + Method method = (Method) methodAndAnnotation[0]; + Annotation businessAction = (Annotation) methodAndAnnotation[1]; //try method if (businessAction != null) { @@ -99,30 +105,41 @@ protected Object doInvoke(InvocationWrapper invocation) throws Throwable { return invocation.proceed(); } - private Annotation parseAnnotation(Method methodKey) throws NoSuchMethodException { - Annotation result = parseAnnotationCache.computeIfAbsent(methodKey, method -> { + /** + * Get try method and the corresponding annotation of TCC mode. + * + * @param invocation + * @return + */ + private Object[] parseAnnotation(InvocationWrapper invocation) { + Object[] results = parseAnnotationCache.computeIfAbsent(invocation.getMethod(), method -> { Annotation twoPhaseBusinessAction = method.getAnnotation(getAnnotationClass()); - if (twoPhaseBusinessAction == null && targetBean.getClass() != null) { - Set> interfaceClasses = ReflectionUtil.getInterfaces(targetBean.getClass()); + Method tryMethod = method; + if (twoPhaseBusinessAction == null && invocation.getTarget() != null) { + Set> interfaceClasses = ReflectionUtil.getInterfaces(invocation.getTarget().getClass()); if (interfaceClasses != null) { for (Class interClass : interfaceClasses) { try { Method m = interClass.getMethod(method.getName(), method.getParameterTypes()); twoPhaseBusinessAction = m.getAnnotation(getAnnotationClass()); if (twoPhaseBusinessAction != null) { - // init common fence clean task if enable useTccFence - initCommonFenceCleanTask(twoPhaseBusinessAction); + tryMethod = m; break; } } catch (NoSuchMethodException e) { - throw new RuntimeException(e); + LOGGER.debug(method.getName() + ", no such method found", e); } } } } - return twoPhaseBusinessAction; + if (twoPhaseBusinessAction == null) { + throw new RuntimeException("No such method with annotation" + getAnnotationClass()); + } + // init common fence clean task if enable useTccFence + initCommonFenceCleanTask(twoPhaseBusinessAction); + return new Object[] {tryMethod, twoPhaseBusinessAction}; }); - return result; + return results; } protected TwoPhaseBusinessActionParam createTwoPhaseBusinessActionParam(Annotation annotation) {