diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageMappingMethodInvoker.java b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageMappingMethodInvoker.java index 8960c0b7cd..c43878b2dc 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageMappingMethodInvoker.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageMappingMethodInvoker.java @@ -20,24 +20,24 @@ import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.integration.core.Message; import org.springframework.integration.message.MessageHandlingException; import org.springframework.integration.message.MethodParameterMessageMapper; import org.springframework.integration.message.OutboundMessageMapper; import org.springframework.integration.util.DefaultMethodInvoker; -import org.springframework.integration.util.DefaultMethodResolver; import org.springframework.integration.util.MethodInvoker; -import org.springframework.integration.util.MethodResolver; -import org.springframework.integration.util.NameResolvingMethodInvoker; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -48,137 +48,93 @@ import org.springframework.util.StringUtils; * * @author Mark Fisher */ -public class MessageMappingMethodInvoker implements MethodInvoker, InitializingBean { +public class MessageMappingMethodInvoker implements MethodInvoker { protected static final Log logger = LogFactory.getLog(MessageMappingMethodInvoker.class); - private volatile boolean methodExpectsMessage; + private final Set methodsExpectingMessage = new HashSet(); private volatile Object object; - private volatile Method method; + private final HandlerMethodResolver methodResolver; - private volatile String methodName; + private final Map> messageMappers = + new HashMap>(); - private volatile OutboundMessageMapper messageMapper; - - private volatile MethodInvoker invoker; - - private volatile boolean initialized; - - private final Object initializationMonitor = new Object(); + private final Map invokers = new HashMap(); public MessageMappingMethodInvoker(Object object, Method method) { Assert.notNull(object, "object must not be null"); Assert.notNull(method, "method must not be null"); this.object = object; - this.method = method; - this.methodName = method.getName(); + this.methodResolver = new StaticHandlerMethodResolver(method); } public MessageMappingMethodInvoker(Object object, Class annotationType) { Assert.notNull(object, "object must not be null"); Assert.notNull(annotationType, "annotation type must not be null"); this.object = object; - MethodResolver methodResolver = new DefaultMethodResolver(annotationType); - this.method = methodResolver.findMethod(object); - Assert.notNull(method, "unable to resolve method for annotation [" + annotationType.getSimpleName() - + "] on target class [" + object.getClass() + "]"); + this.methodResolver = this.createResolverForAnnotation(annotationType); } public MessageMappingMethodInvoker(Object object, String methodName) { Assert.notNull(object, "object must not be null"); Assert.notNull(methodName, "methodName must not be null"); this.object = object; - this.methodName = methodName; + this.methodResolver = this.createResolverForMethodName(methodName); } - public void afterPropertiesSet() { - synchronized (this.initializationMonitor) { - if (this.initialized) { - return; - } - if (this.method == null) { - final List candidates = new ArrayList(); - ReflectionUtils.doWithMethods(this.object.getClass(), new ReflectionUtils.MethodCallback() { - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - if (method.getName().equals(MessageMappingMethodInvoker.this.methodName)) { - candidates.add(method); - } - } - }); - if (candidates.size() == 0) { - throw new IllegalArgumentException("unable to find a candidate method named '" - + this.methodName + "'" + " on target class [" + this.object.getClass() + "]"); - } - else if (candidates.size() == 1) { - this.method = candidates.get(0); - } - } - if (this.method != null) { - Class[] parameterTypes = this.method.getParameterTypes(); - Assert.isTrue(parameterTypes.length > 0, "method must accept at least one parameter"); - if (parameterTypes.length == 1 && Message.class.isAssignableFrom(parameterTypes[0])) { - this.methodExpectsMessage = true; - } - this.invoker = new DefaultMethodInvoker(this.object, this.method); - this.messageMapper = new MethodParameterMessageMapper(this.method); - } - else { - // TODO: resolve the candidate method and/or create a dynamic resolver - this.invoker = new NameResolvingMethodInvoker(this.object, this.methodName); - } - this.initialized = true; - } - } - public Object invokeMethod(Object... args) { - if (!this.initialized) { - this.afterPropertiesSet(); - } Assert.notEmpty(args, "argument array must not be empty"); - Message message = null; - if (args.length == 1 && args[0] != null && (args[0] instanceof Message)) { - message = (Message) args[0]; - if (message.getPayload() == null) { - if (logger.isDebugEnabled()) { - logger.debug("received null payload"); - } - return null; + Assert.isTrue(args.length == 1 && args[0] != null && (args[0] instanceof Message), + "Argument array must contain a single Message instance."); + Message message = (Message) args[0]; + if (message.getPayload() == null) { + if (logger.isDebugEnabled()) { + logger.debug("received null payload"); } - args = this.createArgumentArrayFromMessage(message); + return null; } + Method method = this.methodResolver.resolveHandlerMethod(message); + args = this.createArgumentArrayFromMessage(method, message); try { - return this.doInvokeMethod(args, message); + return this.doInvokeMethod(method, args, message); } catch (InvocationTargetException e) { if (e.getCause() != null && e.getCause() instanceof RuntimeException) { throw (RuntimeException) e.getCause(); } throw new MessageHandlingException(message, - "method '" + this.methodName + "' threw an Exception.", e.getCause()); + "method '" + method + "' threw an Exception.", e.getCause()); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new MessageHandlingException(message, "Failed to invoke method '" - + this.methodName + "' with arguments: " + ObjectUtils.nullSafeToString(args), e); + + method + "' with arguments: " + ObjectUtils.nullSafeToString(args), e); } } - private Object doInvokeMethod(Object[] args, Message message) throws Exception { + private Object doInvokeMethod(Method method, Object[] args, Message message) throws Exception { Object result = null; + MethodInvoker invoker = null; try { - result = this.invoker.invokeMethod(args); + invoker = this.invokers.get(method); + if (invoker == null) { + invoker = new DefaultMethodInvoker(this.object, method); + this.invokers.put(method, invoker); + } + result = invoker.invokeMethod(args); } - catch (NoSuchMethodException e) { + catch (IllegalArgumentException e) { try { if (message != null) { - result = this.invoker.invokeMethod(message); - this.methodExpectsMessage = true; + result = invoker.invokeMethod(message); + // TODO: set this on the invoker + this.methodsExpectingMessage.add(method); } } catch (NoSuchMethodException e2) { @@ -189,10 +145,10 @@ public class MessageMappingMethodInvoker implements MethodInvoker, InitializingB return result; } - private Object[] createArgumentArrayFromMessage(Message message) { + private Object[] createArgumentArrayFromMessage(Method method, Message message) { Object args[] = null; - Object mappingResult = this.methodExpectsMessage - ? message : this.resolveParameters(message); + Object mappingResult = this.methodsExpectingMessage.contains(method) + ? message : this.resolveParameters(method, message); if (mappingResult != null && mappingResult.getClass().isArray() && (Object.class.isAssignableFrom(mappingResult.getClass().getComponentType()))) { args = (Object[]) mappingResult; @@ -203,15 +159,54 @@ public class MessageMappingMethodInvoker implements MethodInvoker, InitializingB return args; } - private Object[] resolveParameters(Message message) { - if (this.messageMapper != null) { - return this.messageMapper.fromMessage(message); + private Object[] resolveParameters(Method method, Message message) { + OutboundMessageMapper mapper = this.messageMappers.get(method); + if (mapper == null) { + mapper = new MethodParameterMessageMapper(method); + this.messageMappers.put(method, mapper); } - return new Object[] { message.getPayload() }; + return mapper.fromMessage(message); } - public String toString() { - return "invoker:" + this.object + "." + this.methodName; + private HandlerMethodResolver createResolverForMethodName(String methodName) { + List methodsWithName = new ArrayList(); + Method[] defaultCandidateMethods = HandlerMethodUtils.getCandidateHandlerMethods(this.object); + for (Method method : defaultCandidateMethods) { + if (method.getName().equals(methodName)) { + methodsWithName.add(method); + } + } + Assert.notEmpty(methodsWithName, "Failed to find any valid Message-handling methods named '" + + methodName + "' on target class [" + this.object.getClass() + "]."); + if (methodsWithName.size() == 1) { + return new StaticHandlerMethodResolver(methodsWithName.get(0)); + } + return new PayloadTypeMatchingHandlerMethodResolver(methodsWithName.toArray(new Method[methodsWithName.size()])); + } + + private HandlerMethodResolver createResolverForAnnotation(Class annotationType) { + List methodsWithAnnotation = new ArrayList(); + Method[] defaultCandidateMethods = HandlerMethodUtils.getCandidateHandlerMethods(this.object); + for (Method method : defaultCandidateMethods) { + Annotation annotation = AnnotationUtils.getAnnotation(method, annotationType); + if (annotation != null) { + methodsWithAnnotation.add(method); + } + } + Method[] candidateMethods = (methodsWithAnnotation.size() == 0) ? null + : methodsWithAnnotation.toArray(new Method[methodsWithAnnotation.size()]); + if (candidateMethods == null) { + if (logger.isInfoEnabled()) { + logger.info("Failed to find any valid Message-handling methods with annotation [" + + annotationType + "] on target class [" + this.object.getClass() + "]. " + + "Method-resolution will be applied to all eligible methods."); + } + candidateMethods = defaultCandidateMethods; + } + if (candidateMethods.length == 1) { + return new StaticHandlerMethodResolver(candidateMethods[0]); + } + return new PayloadTypeMatchingHandlerMethodResolver(candidateMethods); } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/router/MethodInvokingRouter.java b/org.springframework.integration/src/main/java/org/springframework/integration/router/MethodInvokingRouter.java index db5eba532e..ba2dee0696 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/router/MethodInvokingRouter.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/router/MethodInvokingRouter.java @@ -22,7 +22,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import org.springframework.beans.factory.InitializingBean; import org.springframework.integration.annotation.Router; import org.springframework.integration.channel.ChannelResolver; import org.springframework.integration.core.Message; @@ -40,7 +39,7 @@ import org.springframework.util.Assert; * * @author Mark Fisher */ -public class MethodInvokingRouter extends AbstractMessageRouter implements InitializingBean { +public class MethodInvokingRouter extends AbstractMessageRouter { private final MessageMappingMethodInvoker invoker; @@ -68,10 +67,6 @@ public class MethodInvokingRouter extends AbstractMessageRouter implements Initi this.channelResolver = channelResolver; } - public void afterPropertiesSet() throws Exception { - this.invoker.afterPropertiesSet(); - } - @Override protected final Collection determineTargetChannels(Message message) { Object result = this.invoker.invokeMethod(message); diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/splitter/MethodInvokingSplitter.java b/org.springframework.integration/src/main/java/org/springframework/integration/splitter/MethodInvokingSplitter.java index 7cb9b979be..c7d3d9efcf 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/splitter/MethodInvokingSplitter.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/splitter/MethodInvokingSplitter.java @@ -18,7 +18,6 @@ package org.springframework.integration.splitter; import java.lang.reflect.Method; -import org.springframework.beans.factory.InitializingBean; import org.springframework.integration.annotation.Splitter; import org.springframework.integration.core.Message; import org.springframework.integration.handler.MessageMappingMethodInvoker; @@ -32,7 +31,7 @@ import org.springframework.integration.handler.MessageMappingMethodInvoker; * * @author Mark Fisher */ -public class MethodInvokingSplitter extends AbstractMessageSplitter implements InitializingBean { +public class MethodInvokingSplitter extends AbstractMessageSplitter { private final MessageMappingMethodInvoker invoker; @@ -50,10 +49,6 @@ public class MethodInvokingSplitter extends AbstractMessageSplitter implements I } - public void afterPropertiesSet() throws Exception { - this.invoker.afterPropertiesSet(); - } - @Override protected Object splitMessage(Message message) { return this.invoker.invokeMethod(message); diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/endpoint/CorrelationIdTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/endpoint/CorrelationIdTests.java index d2e75db601..d4d3e2b9fb 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/endpoint/CorrelationIdTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/endpoint/CorrelationIdTests.java @@ -104,7 +104,6 @@ public class CorrelationIdTests { MethodInvokingSplitter splitter = new MethodInvokingSplitter( new TestBean(), TestBean.class.getMethod("split", String.class)); splitter.setOutputChannel(testChannel); - splitter.afterPropertiesSet(); splitter.handleMessage(message); Message reply1 = testChannel.receive(100); Message reply2 = testChannel.receive(100); diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/filter/MethodInvokingSelectorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/filter/MethodInvokingSelectorTests.java index ec40dd6823..e75f2d4deb 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/filter/MethodInvokingSelectorTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/filter/MethodInvokingSelectorTests.java @@ -70,8 +70,7 @@ public class MethodInvokingSelectorTests { public void noArgMethodWithMethodReference() throws Exception { TestBean testBean = new TestBean(); Method method = testBean.getClass().getMethod("noArgs", new Class[] {}); - MethodInvokingSelector selector = new MethodInvokingSelector(testBean, method); - selector.accept(new StringMessage("test")); + new MethodInvokingSelector(testBean, method); } @Test(expected = IllegalArgumentException.class) diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodInvokingMessageHandlerTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodInvokingMessageHandlerTests.java index 543840bfbf..2a34ac18a8 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodInvokingMessageHandlerTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodInvokingMessageHandlerTests.java @@ -42,14 +42,12 @@ public class MethodInvokingMessageHandlerTests { @Test public void validMethod() { MethodInvokingMessageHandler handler = new MethodInvokingMessageHandler(new TestSink(), "validMethod"); - handler.afterPropertiesSet(); handler.handleMessage(new GenericMessage("test")); } @Test(expected = IllegalArgumentException.class) public void invalidMethodWithNoArgs() { - MethodInvokingMessageHandler handler = new MethodInvokingMessageHandler(new TestSink(), "invalidMethodWithNoArgs"); - handler.afterPropertiesSet(); + new MethodInvokingMessageHandler(new TestSink(), "invalidMethodWithNoArgs"); } @Test(expected = MessagingException.class) @@ -57,7 +55,6 @@ public class MethodInvokingMessageHandlerTests { Message message = new StringMessage("test"); try { MethodInvokingMessageHandler handler = new MethodInvokingMessageHandler(new TestSink(), "methodWithReturnValue"); - handler.afterPropertiesSet(); handler.handleMessage(message); } catch (MessagingException e) { @@ -68,8 +65,7 @@ public class MethodInvokingMessageHandlerTests { @Test(expected = IllegalArgumentException.class) public void noMatchingMethodName() { - MethodInvokingMessageHandler handler = new MethodInvokingMessageHandler(new TestSink(), "noSuchMethod"); - handler.afterPropertiesSet(); + new MethodInvokingMessageHandler(new TestSink(), "noSuchMethod"); } @Test diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/splitter/MethodInvokingSplitterTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/splitter/MethodInvokingSplitterTests.java index 3ead1fb6de..0681e739ae 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/splitter/MethodInvokingSplitterTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/splitter/MethodInvokingSplitterTests.java @@ -396,8 +396,8 @@ public class MethodInvokingSplitterTests { } @Test(expected = IllegalArgumentException.class) - public void multipleAnnotations() { - new MethodInvokingSplitter(new MultipleAnnotationTestBean()); + public void ambiguousTypeMatch() { + new MethodInvokingSplitter(new AmbiguousTypeMatchTestBean()); } @Test @@ -515,7 +515,7 @@ public class MethodInvokingSplitterTests { } - public static class MultipleAnnotationTestBean { + public static class AmbiguousTypeMatchTestBean { @Splitter public String[] method1(String input) {