From c018b9dec3e3f122952fe5c5a170cc4b8054a68c Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 17 Oct 2022 16:53:52 -0400 Subject: [PATCH] GH-3912: Handler: ignore Groovy generated methods Fixes https://github.com/spring-projects/spring-integration/issues/3912 **Cherry-pick to `5.5.x`** --- .../support/MessagingMethodInvokerHelper.java | 67 ++++++++++--------- .../groovy/dsl/test/GroovyDslTests.groovy | 40 ++++++++++- 2 files changed, 73 insertions(+), 34 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/support/MessagingMethodInvokerHelper.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/support/MessagingMethodInvokerHelper.java index 01711466755..47e375e3ab4 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/support/MessagingMethodInvokerHelper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/support/MessagingMethodInvokerHelper.java @@ -53,6 +53,7 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.ConversionFailedException; @@ -759,7 +760,7 @@ else if (!Modifier.isPublic(method1.getModifiers())) { ambiguousFallbackType, ambiguousFallbackMessageGenericType, matchesAnnotation, handlerMethod1); } - }, new UniqueMethodFilter(targetClass)); + }, ((ReflectionUtils.MethodFilter) this::isHandlerMethod).and(new UniqueMethodFilter(targetClass))); if (candidateMethods.isEmpty() && candidateMessageMethods.isEmpty() && fallbackMethods.isEmpty() && fallbackMessageMethods.isEmpty()) { @@ -767,36 +768,16 @@ else if (!Modifier.isPublic(method1.getModifiers())) { } } - @Nullable - private HandlerMethod obtainHandlerMethodIfAny(Method methodToProcess) { - HandlerMethod handlerMethodToUse = null; - if (isMethodEligible(methodToProcess)) { - try { - handlerMethodToUse = createHandlerMethod( - AopUtils.selectInvocableMethod(methodToProcess, ClassUtils.getUserClass(this.targetObject))); - } - catch (Exception ex) { - LOGGER.debug(ex, "Method [" + methodToProcess + "] is not eligible for Message handling."); - return null; - } - - if (AnnotationUtils.getAnnotation(methodToProcess, Default.class) != null) { - Assert.state(this.defaultHandlerMethod == null, - () -> "Only one method can be @Default, but there are more for: " + this.targetObject); - this.defaultHandlerMethod = handlerMethodToUse; - } - } - - return handlerMethodToUse; - } - - private boolean isMethodEligible(Method methodToProcess) { - return !(methodToProcess.isBridge() || // NOSONAR boolean complexity - isMethodDefinedOnObjectClass(methodToProcess) || - methodToProcess.getDeclaringClass().equals(Proxy.class) || - (this.requiresReply && void.class.equals(methodToProcess.getReturnType())) || - (this.methodName != null && !this.methodName.equals(methodToProcess.getName())) || - (this.methodName == null && isPausableMethod(methodToProcess))); + private boolean isHandlerMethod(Method method) { + Class declaringClass = method.getDeclaringClass(); + return !(method.isSynthetic() || + ReflectionUtils.isObjectMethod(method) || + AnnotatedElementUtils.isAnnotated(method, "groovy.transform.Generated") || + declaringClass.getName().equals("groovy.lang.GroovyObject") || + declaringClass.equals(Proxy.class) || + (this.requiresReply && void.class.equals(method.getReturnType())) || + (this.methodName != null && !this.methodName.equals(method.getName())) || + (this.methodName == null && isPausableMethod(method))); } private boolean isPausableMethod(Method pausableMethod) { @@ -811,6 +792,27 @@ private boolean isPausableMethod(Method pausableMethod) { return pausable; } + @Nullable + private HandlerMethod obtainHandlerMethodIfAny(Method methodToProcess) { + HandlerMethod handlerMethodToUse; + try { + handlerMethodToUse = createHandlerMethod( + AopUtils.selectInvocableMethod(methodToProcess, ClassUtils.getUserClass(this.targetObject))); + } + catch (Exception ex) { + LOGGER.debug(ex, "Method [" + methodToProcess + "] is not eligible for Message handling."); + return null; + } + + if (AnnotationUtils.getAnnotation(methodToProcess, Default.class) != null) { + Assert.state(this.defaultHandlerMethod == null, + () -> "Only one method can be @Default, but there are more for: " + this.targetObject); + this.defaultHandlerMethod = handlerMethodToUse; + } + + return handlerMethodToUse; + } + private void populateHandlerMethod(Map, HandlerMethod> candidateMethods, Map, HandlerMethod> candidateMessageMethods, Map, HandlerMethod> fallbackMethods, Map, HandlerMethod> fallbackMessageMethods, AtomicReference> ambiguousFallbackType, @@ -1013,8 +1015,7 @@ private HandlerMethod findClosestMatch(Class payloadType) { private static boolean isMethodDefinedOnObjectClass(Method method) { return method != null && // NOSONAR - (method.getDeclaringClass().equals(Object.class) || ReflectionUtils.isEqualsMethod(method) || - ReflectionUtils.isHashCodeMethod(method) || ReflectionUtils.isToStringMethod(method) || + (ReflectionUtils.isObjectMethod(method) || AopUtils.isFinalizeMethod(method) || (method.getName().equals("clone") && method.getParameterTypes().length == 0)); } diff --git a/spring-integration-groovy/src/test/groovy/org/springframework/integration/groovy/dsl/test/GroovyDslTests.groovy b/spring-integration-groovy/src/test/groovy/org/springframework/integration/groovy/dsl/test/GroovyDslTests.groovy index b6431c1a75e..a7b38e396fd 100644 --- a/spring-integration-groovy/src/test/groovy/org/springframework/integration/groovy/dsl/test/GroovyDslTests.groovy +++ b/spring-integration-groovy/src/test/groovy/org/springframework/integration/groovy/dsl/test/GroovyDslTests.groovy @@ -16,7 +16,7 @@ package org.springframework.integration.groovy.dsl.test - +import groovy.transform.CompileStatic import org.junit.jupiter.api.Test import org.springframework.beans.factory.BeanFactory import org.springframework.beans.factory.annotation.Autowired @@ -45,6 +45,7 @@ import reactor.core.publisher.Flux import reactor.test.StepVerifier import java.time.Duration +import java.util.concurrent.atomic.AtomicReference import java.util.function.Function import static org.springframework.integration.groovy.dsl.IntegrationGroovyDsl.integrationFlow @@ -196,6 +197,19 @@ class GroovyDslTests { assert this.wireTapChannel.receive(10_000)?.payload == 'test' } + @Autowired + @Qualifier('externalServiceFlow.input') + private MessageChannel externalServiceFlowInput + + @Autowired + GroovyTestService groovyTestService + + @Test + void 'handle service'() { + this.externalServiceFlowInput.send(new GenericMessage('test')) + assert groovyTestService.result.get() == 'TEST' + } + @Configuration @EnableIntegration static class Config { @@ -301,6 +315,30 @@ class GroovyDslTests { } } + @Bean + myService() { + new GroovyTestService() + } + + @Bean + externalServiceFlow(GroovyTestService groovyTestService) { + integrationFlow { + handle groovyTestService + } + + } + + } + + @CompileStatic + static class GroovyTestService { + + AtomicReference result = new AtomicReference<>() + + void handlePayload(String payload) { + result.set payload.toUpperCase() + } + } }