INT-1834: Register Beans for Annotated Handlers

JIRA: https://jira.springsource.org/browse/INT-1834

The general issue has been raised about `MessageHistory` for annotated endpoints (`@ServiceActivator`, `@Transformer` etc.)

* Register generated `MessageHandler`s as beans with concrete names based on component, method and annotation.
* Add tests and docs

In addition revert the version for JRuby to 1.7.8 as other latests versions have a NPE issue around `System.console()`

Doc Polishing
This commit is contained in:
Artem Bilan
2014-03-03 19:22:36 +02:00
committed by Gary Russell
parent 12287056d3
commit 5d2820dfbe
13 changed files with 46 additions and 29 deletions

View File

@@ -78,7 +78,7 @@ subprojects { subproject ->
javaxMailVersion = '1.4.7'
jmsSpecVersion = '1.1.1'
jpa21ApiVersion = '1.0.0.Final'
jrubyVersion = '1.7.10'
jrubyVersion = '1.7.8'
jschVersion = '0.1.50'
jsonpathVersion = '0.9.1'
junitVersion = '4.11'

View File

@@ -32,6 +32,8 @@ public final class IntegrationConfigUtils {
public static final String BASE_PACKAGE = "org.springframework.integration";
public static final String HANDLER_ALIAS_SUFFIX = ".handler";
public static void registerSpelFunctionBean(BeanDefinitionRegistry registry, String functionId, String className,
String methodSignature) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpelFunctionFactoryBean.class)

View File

@@ -25,11 +25,11 @@ import java.util.List;
import org.aopalliance.aop.Advice;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.integration.config.IntegrationConfigUtils;
import org.springframework.integration.context.Orderable;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
@@ -40,6 +40,7 @@ import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.core.DestinationResolver;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@@ -70,7 +71,7 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
@Override
public Object postProcess(Object bean, String beanName, Method method, T annotation) {
MessageHandler handler = this.createHandler(bean, method, annotation);
setAdviceChainIfPresent(beanName, annotation, handler);
this.setAdviceChainIfPresent(beanName, annotation, handler);
if (handler instanceof Orderable) {
Order orderAnnotation = AnnotationUtils.findAnnotation(method, Order.class);
if (orderAnnotation != null) {
@@ -78,7 +79,10 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
}
}
if (beanFactory instanceof ConfigurableListableBeanFactory) {
handler = (MessageHandler) ((ConfigurableListableBeanFactory) beanFactory).initializeBean(handler, "_initHandlerFor_" + beanName);
String handlerBeanName = this.generateHandlerBeanName(beanName, method, annotation.annotationType());
ConfigurableListableBeanFactory listableBeanFactory = (ConfigurableListableBeanFactory) beanFactory;
listableBeanFactory.registerSingleton(handlerBeanName, handler);
handler = (MessageHandler) listableBeanFactory.initializeBean(handler, handlerBeanName);
}
AbstractEndpoint endpoint = this.createEndpoint(handler, annotation);
if (endpoint != null) {
@@ -131,16 +135,23 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
if (StringUtils.hasText(inputChannelName)) {
MessageChannel inputChannel = this.channelResolver.resolveDestination(inputChannelName);
Assert.notNull(inputChannel, "failed to resolve inputChannel '" + inputChannelName + "'");
Assert.isTrue(inputChannel instanceof SubscribableChannel,
Assert.isInstanceOf(SubscribableChannel.class, inputChannel,
"The input channel for an Annotation-based endpoint must be a SubscribableChannel.");
endpoint = new EventDrivenConsumer((SubscribableChannel) inputChannel, handler);
if (handler instanceof BeanFactoryAware) {
((BeanFactoryAware) handler).setBeanFactory(this.beanFactory);
}
}
return endpoint;
}
private String generateHandlerBeanName(String originalBeanName, Method method, Class<? extends Annotation> annotationType) {
String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(annotationType);
String name = baseName;
int count = 1;
while (this.beanFactory.containsBean(name)) {
name = baseName + "#" + (++count);
}
return name + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX;
}
/**
* Subclasses must implement this method to create the MessageHandler.
*

View File

@@ -140,7 +140,7 @@ public class ChainParser extends AbstractConsumerEndpointParser {
return null;
}
else {
holder = new BeanDefinitionHolder(beanDefinition, handlerComponentName + IntegrationNamespaceUtils.HANDLER_ALIAS_SUFFIX);
holder = new BeanDefinitionHolder(beanDefinition, handlerComponentName + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX);
}
}

View File

@@ -61,7 +61,6 @@ public abstract class IntegrationNamespaceUtils {
public static final String METHOD_ATTRIBUTE = "method";
public static final String ORDER = "order";
public static final String EXPRESSION_ATTRIBUTE = "expression";
public static final String HANDLER_ALIAS_SUFFIX = ".handler";
public static final String REQUEST_HANDLER_ADVICE_CHAIN = "request-handler-advice-chain";
public static final String AUTO_STARTUP = "auto-startup";
public static final String PHASE = "phase";
@@ -382,7 +381,7 @@ public abstract class IntegrationNamespaceUtils {
String[] handlerAlias = null;
String id = element.getAttribute(ID_ATTRIBUTE);
if (StringUtils.hasText(id)) {
handlerAlias = new String[] {id + HANDLER_ALIAS_SUFFIX};
handlerAlias = new String[] {id + IntegrationConfigUtils.HANDLER_ALIAS_SUFFIX};
}
return handlerAlias;
}

View File

@@ -6,16 +6,6 @@
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<message-history tracked-components="publishedChannel,input"/>
<beans:bean id="messageHistoryConfigurer"
class="org.springframework.integration.history.MessageHistoryConfigurer">
<beans:property name="componentNamePatterns">
<beans:array>
<beans:value> input </beans:value>
<beans:value> publishedChannel </beans:value>
</beans:array>
</beans:property>
</beans:bean>
<message-history tracked-components="publishedChannel,input,*AnnotationTestService*"/>
</beans:beans>

View File

@@ -93,6 +93,7 @@ public class EnableIntegrationTests {
assertNotNull(messageHistory);
String messageHistoryString = messageHistory.toString();
assertThat(messageHistoryString, Matchers.containsString("input"));
assertThat(messageHistoryString, Matchers.containsString("AnnotationTestService.handle.serviceActivator.handler"));
assertThat(messageHistoryString, Matchers.not(Matchers.containsString("output")));
receive = this.publishedChannel.receive(1000);
@@ -132,7 +133,7 @@ public class EnableIntegrationTests {
@IntegrationComponentScan
@EnableIntegration
@PropertySource("classpath:org/springframework/integration/configuration/EnableIntegrationTests.properties")
@EnableMessageHistory({"input", "publishedChannel"})
@EnableMessageHistory({"input", "publishedChannel", "*AnnotationTestService*"})
public static class ContextConfiguration {
@Bean

View File

@@ -1 +1 @@
message.history.tracked.components=input, publishedChannel
message.history.tracked.components=input, publishedChannel, *AnnotationTestService*

View File

@@ -2,7 +2,7 @@
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
@@ -14,7 +14,7 @@
<channel id="inMethodInvoking" />
<channel id="out" />
<splitter ref="splitterBeanXmlConfig" input-channel="inMethodInvoking"
<splitter id="splitter" ref="splitterBeanXmlConfig" input-channel="inMethodInvoking"
method="split" output-channel="out" />
<splitter input-channel="inDefault" output-channel="out" />
@@ -29,4 +29,4 @@
<beans:bean class="org.springframework.integration.splitter.SplitterIntegrationTests$Receiver" />
</beans:beans>
</beans:beans>

View File

@@ -29,6 +29,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -65,6 +66,7 @@ public class SplitterIntegrationTests {
MessageChannel inDelimiters;
@Autowired
@Qualifier("splitter.handler")
MethodInvokingSplitter splitter;
private String sentence = "The quick brown fox jumped over the lazy dog";

View File

@@ -326,6 +326,15 @@ public class FooService {
still available. For more detail see <xref linkend="bridge"/>.
</note>
</para>
<para>
The processing of these annotations creates the same beans (<classname>EventDrivenConsumer</classname>s and
<interfacename>MessageHandler</interfacename>s) as with similar xml components. The bean names are generated
with this pattern:
<code>[componentName].[methodName].[annotationClassShortName]</code> for the <classname>EventDrivenConsumer</classname>
endpoint and the same name with an additional <code>.handler</code> suffix for
the <interfacename>MessageHandler</interfacename> bean. The
<interfacename>MessageHandler</interfacename>s are also eligible to be tracked by <xref linkend="message-history"/>.
</para>
<para>
Also see <xref linkend="advising-with-annotations"/>.
</para>

View File

@@ -70,7 +70,8 @@ assertEquals("sampleChain", chainHistory.get("name"));]]></programlisting>
</para>
<important>
If multiple beans (declared by <code>@EnableMessageHistory</code> and/or <code>&lt;message-history/&gt;</code>) they
all must have identical component name patterns (when trimmed and sorted).
all must have identical component name patterns (when trimmed and sorted). <emphasis role="bold">Do not use a generic
<code>&lt;bean/&gt;</code> definition for the <classname>MessageHistoryConfigurer</classname></emphasis>.
</important>
<note>
Remember that by definition the Message History header is immutable (you can't re-write history, although some try). Therefore, when writing

View File

@@ -40,7 +40,9 @@
<para>
Message history can now be enabled with the <code>@EnableMessageHistory</code> annotation in a
<code>@Configuration</code> class; in addition the message history settings can be modified
by a JMX MBean. For more information, see <xref linkend="message-history"/>.
by a JMX MBean. In addition auto-created <interfacename>MessageHandler</interfacename>s for
annotated endpoints (e.g. <classname>@ServiceActivator</classname>, <classname>@Splitter</classname> etc.)
now are also trackable by <code>MessageHistory</code>. For more information, see <xref linkend="message-history"/>.
</para>
</section>
<section id="4.0-messaging-gateway">