MessagingAnnotationPostProcessor no longer creates a proxy for annotated methods. It still creates and registers an endpoint if an "inputChannel" is specified, but for satisfying a "ref" in an XML-based endpoint configuration, it will be the responsibility of the corresponding consumer to create a Method-invoking adapter instance for the annotated Method. The ServiceActivator performs this role already, checking for a single @ServiceActivaotor-annotated method or falling back to a single public Method that is not declared on the Object class.

This commit is contained in:
Mark Fisher
2008-10-05 18:16:31 +00:00
parent 5c16131ce1
commit 88df7711e0
7 changed files with 145 additions and 63 deletions

View File

@@ -284,6 +284,8 @@ public class DefaultMessageBus implements MessageBus, ApplicationContextAware, A
}
}
// Lifecycle implementation
public boolean isRunning() {
synchronized (this.lifecycleMonitor) {
return this.running;

View File

@@ -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<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> postProcessors =
new HashMap<Class<? extends Annotation>, 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;
}

View File

@@ -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) {

View File

@@ -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 <em>single</em> 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 <code>null</code> if the
* Class contains no Methods with the specified annotation
*
* @throws IllegalArgumentException if more than one Method has the
* specified annotation
*/
public static <T extends Annotation> Method findMethodWithAnnotation(
final Class<?> clazz, final Class<T> annotationType) {
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
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<Method> methods = new ArrayList<Method>();
for (Method method : clazz.getMethods()) {
if (includeMethodsDeclaredOnObject
|| !method.getDeclaringClass().equals(Object.class)) {
methods.add(method);
}
}
return methods.toArray(new Method[methods.size()]);
}
}

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -13,7 +13,7 @@
<channel id="inputChannel"/>
<channel id="outputChannel">
<queue capacity="5"/>
<queue capacity="1"/>
</channel>
<service-activator input-channel="inputChannel" ref="testBean" output-channel="outputChannel"/>