diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/bus/DefaultMessageBus.java b/org.springframework.integration/src/main/java/org/springframework/integration/bus/DefaultMessageBus.java index db60f40601..15edffae28 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/bus/DefaultMessageBus.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/bus/DefaultMessageBus.java @@ -284,6 +284,8 @@ public class DefaultMessageBus implements MessageBus, ApplicationContextAware, A } } + // Lifecycle implementation + public boolean isRunning() { synchronized (this.lifecycleMonitor) { return this.running; diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java index c9ac6aaa92..ac965ecaee 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessor.java @@ -23,13 +23,9 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.AopUtils; -import org.springframework.aop.support.DelegatingIntroductionInterceptor; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; @@ -58,13 +54,12 @@ import org.springframework.util.ReflectionUtils; * @author Mark Fisher * @author Marius Bogoevici */ -public class MessagingAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean, BeanClassLoaderAware { +public class MessagingAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean { private final MessageBus messageBus; private volatile ConfigurableBeanFactory beanFactory; - private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); private final Map, MethodAnnotationPostProcessor> postProcessors = new HashMap, MethodAnnotationPostProcessor>(); @@ -82,10 +77,6 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean this.beanFactory = (ConfigurableBeanFactory) beanFactory; } - public void setBeanClassLoader(ClassLoader beanClassLoader) { - this.beanClassLoader = beanClassLoader; - } - public void afterPropertiesSet() { Assert.notNull(this.beanFactory, "BeanFactory must not be null"); postProcessors.put(Aggregator.class, new AggregatorAnnotationPostProcessor(this.messageBus)); @@ -100,16 +91,13 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean return bean; } - public Object postProcessAfterInitialization(Object bean, final String beanName) throws BeansException { + public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { Assert.notNull(this.beanFactory, "BeanFactory must not be null"); - final Object originalBean = bean; final Class beanClass = this.getBeanClass(bean); if (!this.isStereotype(beanClass)) { // we only post-process stereotype components return bean; } - final ProxyFactory proxyFactory = new ProxyFactory(bean); - final AtomicBoolean isProxy = new AtomicBoolean(false); ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() { @SuppressWarnings("unchecked") public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { @@ -117,35 +105,13 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean for (Annotation annotation : annotations) { MethodAnnotationPostProcessor postProcessor = postProcessors.get(annotation.annotationType()); if (postProcessor != null) { - Object result = postProcessor.postProcess(originalBean, beanName, method, annotation); - if (result != null) { - if (result instanceof MessageEndpoint) { - String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType()); - if (result instanceof BeanNameAware) { - ((BeanNameAware) result).setBeanName(endpointBeanName); - } - beanFactory.registerSingleton(endpointBeanName, result); - } - else { - boolean shouldProxy = false; - Class[] interfaces = ClassUtils.getAllInterfaces(result); - for (Class iface : interfaces) { - if (!iface.getPackage().getName().startsWith("org.springframework.integration")) { - continue; - } - if (proxyFactory.isInterfaceProxied(iface)) { - throw new IllegalStateException("interface [" + iface + "] is already proxied"); - } - shouldProxy = true; - } - if (result instanceof ChannelRegistryAware) { - ((ChannelRegistryAware) result).setChannelRegistry(messageBus); - } - if (shouldProxy) { - proxyFactory.addAdvice(new DelegatingIntroductionInterceptor(result)); - isProxy.set(true); - } + Object result = postProcessor.postProcess(bean, beanName, method, annotation); + if (result != null && result instanceof MessageEndpoint) { + String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType()); + if (result instanceof BeanNameAware) { + ((BeanNameAware) result).setBeanName(endpointBeanName); } + beanFactory.registerSingleton(endpointBeanName, result); } } } @@ -154,9 +120,6 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean if (bean instanceof ChannelRegistryAware) { ((ChannelRegistryAware) bean).setChannelRegistry(messageBus); } - if (isProxy.get()) { - return proxyFactory.getProxy(this.beanClassLoader); - } return bean; } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/endpoint/ServiceActivatorEndpoint.java b/org.springframework.integration/src/main/java/org/springframework/integration/endpoint/ServiceActivatorEndpoint.java index 3dfd65e906..d0218c584e 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/endpoint/ServiceActivatorEndpoint.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/endpoint/ServiceActivatorEndpoint.java @@ -16,10 +16,14 @@ package org.springframework.integration.endpoint; +import java.lang.reflect.Method; + import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.message.Message; import org.springframework.integration.message.MessageHandlingException; import org.springframework.integration.message.MessageMappingMethodInvoker; +import org.springframework.integration.util.MethodUtils; import org.springframework.integration.util.MethodInvoker; import org.springframework.util.Assert; @@ -39,8 +43,18 @@ public class ServiceActivatorEndpoint extends AbstractMessageHandlingEndpoint { this.invoker = invoker; } - public ServiceActivatorEndpoint(Object object) { - this(object, DEFAULT_LISTENER_METHOD); + public ServiceActivatorEndpoint(final Object object) { + Assert.notNull(object, "object must not be null"); + Method method = MethodUtils.findMethodWithAnnotation(object.getClass(), ServiceActivator.class); + if (method == null) { + Method[] methods = MethodUtils.findPublicMethods(object.getClass(), false); + if (methods.length == 1) { + method = methods[0]; + } + } + Assert.notNull(method, "unable to resolve ServiceActivator method on target class [" + + object.getClass() + "]"); + this.invoker = new MessageMappingMethodInvoker(object, method); } public ServiceActivatorEndpoint(Object object, String methodName) { diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/util/MethodUtils.java b/org.springframework.integration/src/main/java/org/springframework/integration/util/MethodUtils.java new file mode 100644 index 0000000000..9602e1eeb0 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/util/MethodUtils.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2008 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. + * 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.springframework.integration.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * Helper methods for detecting Methods. + * + * @author Mark Fisher + */ +public abstract class MethodUtils { + + /** + * Find a single Method on the given Class that contains the + * specified annotation type. + * + * @param clazz the Class instance to check for the annotation + * @param annotationType the Method-level annotation type + * + * @return a single matching Method instance or null if the + * Class contains no Methods with the specified annotation + * + * @throws IllegalArgumentException if more than one Method has the + * specified annotation + */ + public static Method findMethodWithAnnotation( + final Class clazz, final Class annotationType) { + final AtomicReference annotatedMethod = new AtomicReference(); + ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + T annotation = AnnotationUtils.findAnnotation(method, annotationType); + if (annotation != null) { + Assert.isNull(annotatedMethod.get(), "found more than one method on target class [" + + clazz + "] with the annotation type [" + annotationType + "]"); + annotatedMethod.set(method); + } + } + }); + return annotatedMethod.get(); + } + + /** + * Find all public Methods of a given Class. + * + * @param clazz the class to search + * @param includeMethodsDeclaredOnObject whether to include Methods + * that are declared on the Object class + * + * @return array of public Methods + */ + public static Method[] findPublicMethods( + final Class clazz, final boolean includeMethodsDeclaredOnObject) { + final List methods = new ArrayList(); + for (Method method : clazz.getMethods()) { + if (includeMethodsDeclaredOnObject + || !method.getDeclaringClass().equals(Object.class)) { + methods.add(method); + } + } + return methods.toArray(new Method[methods.size()]); + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/TestHandler.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/TestHandler.java index bf31bb050d..7da1749f41 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/TestHandler.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/TestHandler.java @@ -18,6 +18,7 @@ package org.springframework.integration.config; import java.util.concurrent.CountDownLatch; +import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.message.Message; import org.springframework.integration.message.StringMessage; @@ -46,6 +47,7 @@ public class TestHandler { this.replyMessageText = replyMessageText; } + @ServiceActivator public Message handle(Message message) { this.messageString = message.getPayload().toString(); this.latch.countDown(); diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java index 19272af46e..994f22e7a0 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java @@ -44,6 +44,7 @@ import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.MessageChannel; import org.springframework.integration.channel.PollableChannel; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.MessageBusParser; import org.springframework.integration.endpoint.ChannelPoller; import org.springframework.integration.endpoint.ServiceActivatorEndpoint; import org.springframework.integration.message.Message; @@ -51,7 +52,6 @@ import org.springframework.integration.message.MessageConsumer; import org.springframework.integration.message.StringMessage; import org.springframework.integration.scheduling.IntervalTrigger; import org.springframework.integration.scheduling.Trigger; -import org.springframework.integration.util.MethodInvoker; /** * @author Mark Fisher @@ -59,25 +59,32 @@ import org.springframework.integration.util.MethodInvoker; public class MessagingAnnotationPostProcessorTests { @Test - public void testHandlerAnnotation() { + public void testServiceActivatorAnnotation() { GenericApplicationContext context = new GenericApplicationContext(); DefaultMessageBus messageBus = new DefaultMessageBus(); messageBus.setApplicationContext(context); + QueueChannel inputChannel = new QueueChannel(); + inputChannel.setBeanName("inputChannel"); + messageBus.registerChannel(inputChannel); MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(messageBus); postProcessor.setBeanFactory(context.getBeanFactory()); postProcessor.afterPropertiesSet(); - HandlerAnnotatedBean bean = new HandlerAnnotatedBean(); - Object result = postProcessor.postProcessAfterInitialization(bean, "testBean"); - assertTrue(result instanceof MethodInvoker); + ServiceActivatorAnnotatedBean bean = new ServiceActivatorAnnotatedBean(); + postProcessor.postProcessAfterInitialization(bean, "testBean"); + assertTrue(context.containsBean("testBean.test.serviceActivator")); + Object endpoint = context.getBean("testBean.test.serviceActivator"); + assertTrue(endpoint instanceof org.springframework.integration.endpoint.MessageEndpoint); } @Test - public void testSimpleHandlerWithContext() throws Exception { + public void testServiceActivatorWithContext() throws Exception { AbstractApplicationContext context = new ClassPathXmlApplicationContext( "serviceActivatorAnnotationPostProcessorTests.xml", this.getClass()); - MethodInvoker invoker = (MethodInvoker) context.getBean("testBean"); - String reply = (String) invoker.invokeMethod(new StringMessage("world")); - assertEquals("hello world", reply); + MessageChannel inputChannel = (MessageChannel) context.getBean("inputChannel"); + PollableChannel outputChannel = (PollableChannel) context.getBean("outputChannel"); + inputChannel.send(new StringMessage("world")); + Message reply = outputChannel.receive(0); + assertEquals("hello world", reply.getPayload()); context.stop(); } @@ -367,15 +374,23 @@ public class MessagingAnnotationPostProcessorTests { @Test public void testTransformer() { GenericApplicationContext context = new GenericApplicationContext(); + DirectChannel inputChannel = new DirectChannel(); + inputChannel.setBeanName("inputChannel"); + context.getBeanFactory().registerSingleton("inputChannel", inputChannel); + QueueChannel outputChannel = new QueueChannel(); + outputChannel.setBeanName("outputChannel"); + context.getBeanFactory().registerSingleton("outputChannel", outputChannel); DefaultMessageBus messageBus = new DefaultMessageBus(); + context.getBeanFactory().registerSingleton(MessageBusParser.MESSAGE_BUS_BEAN_NAME, messageBus); messageBus.setApplicationContext(context); MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(messageBus); postProcessor.setBeanFactory(context.getBeanFactory()); postProcessor.afterPropertiesSet(); TransformerAnnotationTestBean testBean = new TransformerAnnotationTestBean(); - org.springframework.integration.transformer.Transformer transformer = - (org.springframework.integration.transformer.Transformer) postProcessor.postProcessAfterInitialization(testBean, "testBean"); - Message reply = transformer.transform(new StringMessage("foo")); + postProcessor.postProcessAfterInitialization(testBean, "testBean"); + context.refresh(); + inputChannel.send(new StringMessage("foo")); + Message reply = outputChannel.receive(0); assertEquals("FOO", reply.getPayload()); } @@ -455,9 +470,9 @@ public class MessagingAnnotationPostProcessorTests { @MessageEndpoint - private static class HandlerAnnotatedBean { + private static class ServiceActivatorAnnotatedBean { - @ServiceActivator + @ServiceActivator(inputChannel="inputChannel") public String test(String s) { return s + s; } @@ -479,7 +494,7 @@ public class MessagingAnnotationPostProcessorTests { @MessageEndpoint private static class TransformerAnnotationTestBean { - @Transformer + @Transformer(inputChannel="inputChannel", outputChannel="outputChannel") public String transformBefore(String input) { return input.toUpperCase(); } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/serviceActivatorAnnotationPostProcessorTests.xml b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/serviceActivatorAnnotationPostProcessorTests.xml index 7a6c2c5520..7f955e3223 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/serviceActivatorAnnotationPostProcessorTests.xml +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/serviceActivatorAnnotationPostProcessorTests.xml @@ -13,7 +13,7 @@ - +