diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index be4b6ce8079..552192eb1d2 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -34,6 +34,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6372](https://github.com/apache/incubator-seata/pull/6372)] fix initializing the sql file postgresql.sql index name conflict - [[#6380](https://github.com/apache/incubator-seata/pull/6380)] fix sql exception when checking for the existence of the UNDO_LOG table on SQL server - [[#6385](https://github.com/apache/incubator-seata/pull/6385)] fix the bug where Role.participant does not execute hooks but clears them. +- [[#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. ### optimize: - [[#6031](https://github.com/apache/incubator-seata/pull/6031)] add a check for the existence of the undolog table diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 61229c5ce66..7fbf2339e1f 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -34,6 +34,7 @@ - [[#6372](https://github.com/apache/incubator-seata/pull/6372)] 修复初始化sql文件postgresql.sql 索引名称冲突问题 - [[#6380](https://github.com/apache/incubator-seata/pull/6380)] 修复针对sql server检查UNDO_LOG表是否存在时的SQL异常 - [[#6385](https://github.com/apache/incubator-seata/pull/6385)] 修复Role.Participant不执行hook但会清理的问题 +- [[#6475](https://github.com/apache/incubator-seata/pull/6475)] 修复TCC模式@BusinessActionContextParamete注解无法将参数设置到BusinessActionContext的问题 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..8c0b6c60c32 --- /dev/null +++ b/spring/src/test/java/org/apache/seata/spring/tcc/TccActionInterceptorHandlerTest.java @@ -0,0 +1,59 @@ +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) {