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 80d74ad683..2bb66cde61 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 @@ -183,7 +183,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator private final Map, HandlerMethod> handlerMessageMethods; - private final List, HandlerMethod>> handlerMethodsList; + private final List, HandlerMethod>> handlerMethodsList = new LinkedList<>(); private HandlerMethod handlerMethod; @@ -258,10 +258,11 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator Assert.notNull(targetObject, "targetObject must not be null"); this.targetObject = targetObject; - createHandlerMethod(); + this.handlerMethod = createHandlerMethod(this.method); this.handlerMethods = null; this.handlerMessageMethods = null; - this.handlerMethodsList = null; + this.handlerMethodsList.add( + Collections.singletonMap(this.handlerMethod.targetParameterType, this.handlerMethod)); setDisplayString(targetObject, method); JsonObjectMapper mapper; @@ -274,6 +275,54 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator this.jsonObjectMapper = mapper; } + private MessagingMethodInvokerHelper(Object targetObject, Class annotationType, + String methodName, Class expectedType, boolean canProcessMessageList) { + + this.annotationType = annotationType; + this.methodName = methodName; + this.canProcessMessageList = canProcessMessageList; + Assert.notNull(targetObject, "targetObject must not be null"); + if (expectedType != null) { + this.expectedType = TypeDescriptor.valueOf(expectedType); + } + else { + this.expectedType = null; + } + this.targetObject = targetObject; + Map, HandlerMethod>> handlerMethodsForTarget = + findHandlerMethodsForTarget(annotationType, methodName, expectedType != null); + Map, HandlerMethod> methods = handlerMethodsForTarget.get(CANDIDATE_METHODS); + Map, HandlerMethod> messageMethods = handlerMethodsForTarget.get(CANDIDATE_MESSAGE_METHODS); + if ((methods.size() == 1 && messageMethods.isEmpty()) || + (messageMethods.size() == 1 && methods.isEmpty())) { + if (methods.size() == 1) { + this.handlerMethod = methods.values().iterator().next(); + } + else { + this.handlerMethod = messageMethods.values().iterator().next(); + } + } + else { + this.handlerMethod = null; + } + + this.handlerMethods = methods; + this.handlerMessageMethods = messageMethods; + //TODO Consider to use global option to determine a precedence of methods + this.handlerMethodsList.add(this.handlerMethods); + this.handlerMethodsList.add(this.handlerMessageMethods); + + setDisplayString(targetObject, methodName); + JsonObjectMapper mapper; + try { + mapper = JsonObjectMapperProvider.newInstance(); + } + catch (IllegalStateException e) { + mapper = null; + } + this.jsonObjectMapper = mapper; + } + /** * A {@code boolean} flag to use SpEL Expression evaluation or {@link InvocableHandlerMethod} * for target method invocation. @@ -346,76 +395,28 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator * Private constructors for internal use */ - private MessagingMethodInvokerHelper(Object targetObject, Class annotationType, - String methodName, Class expectedType, boolean canProcessMessageList) { - - this.annotationType = annotationType; - this.methodName = methodName; - this.canProcessMessageList = canProcessMessageList; - Assert.notNull(targetObject, "targetObject must not be null"); - if (expectedType != null) { - this.expectedType = TypeDescriptor.valueOf(expectedType); - } - else { - this.expectedType = null; - } - this.targetObject = targetObject; - Map, HandlerMethod>> handlerMethodsForTarget = - findHandlerMethodsForTarget(targetObject, annotationType, methodName, expectedType != null); - Map, HandlerMethod> methods = handlerMethodsForTarget.get(CANDIDATE_METHODS); - Map, HandlerMethod> messageMethods = handlerMethodsForTarget.get(CANDIDATE_MESSAGE_METHODS); - if ((methods.size() == 1 && messageMethods.isEmpty()) || - (messageMethods.size() == 1 && methods.isEmpty())) { - if (methods.size() == 1) { - this.handlerMethod = methods.values().iterator().next(); - } - else { - this.handlerMethod = messageMethods.values().iterator().next(); - } - this.handlerMethods = null; - this.handlerMessageMethods = null; - this.handlerMethodsList = null; - } - else { - this.handlerMethod = null; - this.handlerMethods = methods; - this.handlerMessageMethods = messageMethods; - this.handlerMethodsList = new LinkedList<>(); - - //TODO Consider to use global option to determine a precedence of methods - this.handlerMethodsList.add(this.handlerMethods); - this.handlerMethodsList.add(this.handlerMessageMethods); - } - setDisplayString(targetObject, methodName); - JsonObjectMapper mapper; - try { - mapper = JsonObjectMapperProvider.newInstance(); - } - catch (IllegalStateException e) { - mapper = null; - } - this.jsonObjectMapper = mapper; - } - private boolean isProvidedMessageHandlerFactoryBean() { BeanFactory beanFactory = getBeanFactory(); return beanFactory != null && beanFactory.containsBean(IntegrationContextUtils.MESSAGE_HANDLER_FACTORY_BEAN_NAME); } - private void createHandlerMethod() { + private HandlerMethod createHandlerMethod(Method method) { try { - InvocableHandlerMethod invocableHandlerMethod = - this.messageHandlerMethodFactory.createInvocableHandlerMethod(this.targetObject, this.method); - this.handlerMethod = new HandlerMethod(invocableHandlerMethod, this.canProcessMessageList); - this.defaultHandlerMethod = null; - checkSpelInvokerRequired(getTargetClass(this.targetObject), this.method, this.handlerMethod); + InvocableHandlerMethod invocableHandlerMethod = createInvocableHandlerMethod(method); + HandlerMethod handlerMethod = new HandlerMethod(invocableHandlerMethod, this.canProcessMessageList); + checkSpelInvokerRequired(getTargetClass(this.targetObject), method, handlerMethod); + return handlerMethod; } catch (IneligibleMethodException e) { throw new IllegalArgumentException(e); } } + private InvocableHandlerMethod createInvocableHandlerMethod(Method method) { + return this.messageHandlerMethodFactory.createInvocableHandlerMethod(this.targetObject, method); + } + private void setDisplayString(Object targetObject, Object targetMethod) { StringBuilder sb = new StringBuilder(targetObject.getClass().getName()); if (targetMethod instanceof Method) { @@ -472,7 +473,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator if (!this.initialized) { initialize(); } - HandlerMethod candidate = this.findHandlerMethodForParameters(parameters); + HandlerMethod candidate = findHandlerMethodForParameters(parameters); if (candidate == null) { candidate = this.defaultHandlerMethod; } @@ -525,7 +526,13 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator this.messageHandlerMethodFactory = beanFactory.getBean(IntegrationContextUtils.MESSAGE_HANDLER_FACTORY_BEAN_NAME, MessageHandlerMethodFactory.class); - createHandlerMethod(); + this.handlerMethodsList + .stream() + .map(Map::values) + .flatMap(Collection::stream) + .forEach(handlerMethod -> + handlerMethod.replaceInvocableHandlerMethod( + createInvocableHandlerMethod(handlerMethod.invocableHandlerMethod.getMethod()))); } else { if (beanFactory != null && @@ -645,7 +652,6 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator expression.getExpressionString() + " ]"); } } - return invokeExpression(expression, parameters); } } @@ -716,9 +722,8 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator return contentType != null && contentType.toString().contains("json"); } - private Map, HandlerMethod>> findHandlerMethodsForTarget(final Object targetObject, - final Class annotationType, final String methodNameArg, - final boolean requiresReply) { + private Map, HandlerMethod>> findHandlerMethodsForTarget( + final Class annotationType, final String methodNameArg, final boolean requiresReply) { Map, HandlerMethod>> methods = new HashMap<>(); @@ -728,7 +733,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator final Map, HandlerMethod> fallbackMessageMethods = new HashMap<>(); final AtomicReference> ambiguousFallbackType = new AtomicReference<>(); final AtomicReference> ambiguousFallbackMessageGenericType = new AtomicReference<>(); - final Class targetClass = getTargetClass(targetObject); + final Class targetClass = getTargetClass(this.targetObject); final String methodNameToUse; @@ -779,11 +784,8 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator HandlerMethod handlerMethod1; try { method1 = AopUtils.selectInvocableMethod(method1, - org.springframework.util.ClassUtils.getUserClass(targetObject)); - InvocableHandlerMethod invocableHandlerMethod = - this.messageHandlerMethodFactory.createInvocableHandlerMethod(targetObject, method1); - handlerMethod1 = new HandlerMethod(invocableHandlerMethod, this.canProcessMessageList); - checkSpelInvokerRequired(targetClass, method1, handlerMethod1); + org.springframework.util.ClassUtils.getUserClass(this.targetObject)); + handlerMethod1 = createHandlerMethod(method1); } catch (IneligibleMethodException e) { if (logger.isDebugEnabled()) { @@ -800,7 +802,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } if (AnnotationUtils.getAnnotation(method1, Default.class) != null) { Assert.state(this.defaultHandlerMethod == null, - () -> "Only one method can be @Default, but there are more for: " + targetObject); + () -> "Only one method can be @Default, but there are more for: " + this.targetObject); this.defaultHandlerMethod = handlerMethod1; } Class targetParameterType = handlerMethod1.getTargetParameterType(); @@ -850,8 +852,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator if (candidateMethods.isEmpty() && candidateMessageMethods.isEmpty() && fallbackMethods.isEmpty() && fallbackMessageMethods.isEmpty()) { - findSingleSpecifMethodOnInterfacesIfProxy(targetObject, methodNameToUse, candidateMessageMethods, - candidateMethods); + findSingleSpecifMethodOnInterfacesIfProxy(methodNameToUse, candidateMessageMethods, candidateMethods); } if (!candidateMethods.isEmpty() || !candidateMessageMethods.isEmpty()) { @@ -875,7 +876,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator if ("org.springframework.integration.gateway.RequestReplyExchanger".equals(iface.getName())) { frameworkMethods.add(targetClass.getMethod("exchange", Message.class)); if (logger.isDebugEnabled()) { - logger.debug(targetObject.getClass() + + logger.debug(this.targetObject.getClass() + ": Ambiguous fallback methods; using RequestReplyExchanger.exchange()"); } } @@ -886,12 +887,8 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } if (frameworkMethods.size() == 1) { Method frameworkMethod = org.springframework.util.ClassUtils.getMostSpecificMethod( - frameworkMethods.get(0), targetObject.getClass()); - InvocableHandlerMethod invocableHandlerMethod = - this.messageHandlerMethodFactory.createInvocableHandlerMethod(targetObject, - frameworkMethod); - HandlerMethod theHandlerMethod = new HandlerMethod(invocableHandlerMethod, this.canProcessMessageList); - checkSpelInvokerRequired(targetClass, frameworkMethod, theHandlerMethod); + frameworkMethods.get(0), this.targetObject.getClass()); + HandlerMethod theHandlerMethod = createHandlerMethod(frameworkMethod); methods.put(CANDIDATE_METHODS, Collections.singletonMap(Object.class, theHandlerMethod)); methods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods); return methods; @@ -916,17 +913,17 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator return methods; } - private void findSingleSpecifMethodOnInterfacesIfProxy(final Object targetObject, final String methodName, + private void findSingleSpecifMethodOnInterfacesIfProxy(final String methodName, Map, HandlerMethod> candidateMessageMethods, Map, HandlerMethod> candidateMethods) { - if (AopUtils.isAopProxy(targetObject)) { + if (AopUtils.isAopProxy(this.targetObject)) { final AtomicReference targetMethod = new AtomicReference<>(); final AtomicReference> targetClass = new AtomicReference<>(); - Class[] interfaces = ((Advised) targetObject).getProxiedInterfaces(); + Class[] interfaces = ((Advised) this.targetObject).getProxiedInterfaces(); for (Class clazz : interfaces) { ReflectionUtils.doWithMethods(clazz, method1 -> { if (targetMethod.get() != null) { - throw new IllegalStateException("Ambiguous method " + methodName + " on " + targetObject); + throw new IllegalStateException("Ambiguous method " + methodName + " on " + this.targetObject); } else { targetMethod.set(method1); @@ -937,11 +934,8 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator Method theMethod = targetMethod.get(); if (theMethod != null) { theMethod = org.springframework.util.ClassUtils - .getMostSpecificMethod(theMethod, targetObject.getClass()); - InvocableHandlerMethod invocableHandlerMethod = - this.messageHandlerMethodFactory.createInvocableHandlerMethod(targetObject, theMethod); - HandlerMethod theHandlerMethod = new HandlerMethod(invocableHandlerMethod, this.canProcessMessageList); - checkSpelInvokerRequired(targetClass.get(), theMethod, theHandlerMethod); + .getMostSpecificMethod(theMethod, this.targetObject.getClass()); + HandlerMethod theHandlerMethod = createHandlerMethod(theMethod); Class targetParameterType = theHandlerMethod.getTargetParameterType(); if (theHandlerMethod.isMessageMethod()) { if (candidateMessageMethods.containsKey(targetParameterType)) { @@ -1033,7 +1027,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator final Class payloadType = parameters.getFirstParameterType(); - HandlerMethod closestMatch = this.findClosestMatch(payloadType); + HandlerMethod closestMatch = findClosestMatch(payloadType); if (closestMatch != null) { return closestMatch; @@ -1045,7 +1039,6 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator else { return this.handlerMethods.get(Void.class); } - } private HandlerMethod findClosestMatch(Class payloadType) { @@ -1078,10 +1071,10 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator private final String expressionString; - private final InvocableHandlerMethod invocableHandlerMethod; - private final boolean canProcessMessageList; + private InvocableHandlerMethod invocableHandlerMethod; + private volatile Expression expression; private volatile TypeDescriptor targetParameterTypeDescriptor; @@ -1109,6 +1102,9 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator this.expressionString = generateExpression(this.invocableHandlerMethod.getMethod()); } + void replaceInvocableHandlerMethod(InvocableHandlerMethod newInvocableHandlerMethod) { + this.invocableHandlerMethod = newInvocableHandlerMethod; + } @SuppressWarnings("unchecked") public T invoke(ParametersWrapper parameters) throws Exception { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java index a893087fd1..24aeb9d861 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -170,7 +169,7 @@ public class AggregatorParserTests { Object handlerMethods = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor .getPropertyValue("outputProcessor")).getPropertyValue("processor")).getPropertyValue("delegate")) .getPropertyValue("handlerMethods"); - assertNull(handlerMethods); + assertNotNull(handlerMethods); Object handlerMethod = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor .getPropertyValue("outputProcessor")).getPropertyValue("processor")).getPropertyValue("delegate")) .getPropertyValue("handlerMethod"); @@ -250,7 +249,7 @@ public class AggregatorParserTests { MessagingMethodInvokerHelper methodInvokerHelper = TestUtils.getPropertyValue(releaseStrategy, "adapter.delegate", MessagingMethodInvokerHelper.class); Object handlerMethods = TestUtils.getPropertyValue(methodInvokerHelper, "handlerMethods"); - assertNull(handlerMethods); + assertNotNull(handlerMethods); Object handlerMethod = TestUtils.getPropertyValue(methodInvokerHelper, "handlerMethod"); assertTrue(handlerMethod.toString().contains("checkCompleteness")); input.send(createMessage(1L, "correlationId", 4, 0, null)); @@ -261,7 +260,7 @@ public class AggregatorParserTests { Assert.assertNull(reply); input.send(createMessage(5L, "correlationId", 4, 3, null)); reply = outputChannel.receive(0); - Assert.assertNotNull(reply); + assertNotNull(reply); assertEquals(11L, reply.getPayload()); } @@ -275,7 +274,7 @@ public class AggregatorParserTests { DirectFieldAccessor releaseStrategyAccessor = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategy) .getPropertyValue("adapter")).getPropertyValue("delegate")); Object handlerMethods = releaseStrategyAccessor.getPropertyValue("handlerMethods"); - assertNull(handlerMethods); + assertNotNull(handlerMethods); Object handlerMethod = releaseStrategyAccessor.getPropertyValue("handlerMethod"); assertTrue(handlerMethod.toString().contains("checkCompleteness")); input.send(createMessage(1L, "correlationId", 4, 0, null)); @@ -286,7 +285,7 @@ public class AggregatorParserTests { Assert.assertNull(reply); input.send(createMessage(5L, "correlationId", 4, 3, null)); reply = outputChannel.receive(0); - Assert.assertNotNull(reply); + assertNotNull(reply); assertEquals(11L, reply.getPayload()); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationsWithBeanAnnotationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationsWithBeanAnnotationTests.java index a89a51f11f..8e38ca3186 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationsWithBeanAnnotationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationsWithBeanAnnotationTests.java @@ -68,6 +68,7 @@ import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.config.EnableMessageHistory; +import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.core.MessageSelector; import org.springframework.integration.core.MessageSource; import org.springframework.integration.endpoint.AbstractEndpoint; @@ -81,6 +82,8 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; +import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory; import org.springframework.messaging.support.ErrorMessage; import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.MessageBuilder; @@ -244,6 +247,11 @@ public class MessagingAnnotationsWithBeanAnnotationTests { private static final ExpressionParser PARSER = new SpelExpressionParser(); + @Bean(IntegrationContextUtils.MESSAGE_HANDLER_FACTORY_BEAN_NAME) + public MessageHandlerMethodFactory messageHandlerMethodFactory() { + return new DefaultMessageHandlerMethodFactory(); + } + @Bean public AtomicInteger counter() { return new AtomicInteger();