JMS annotation-driven endpoints.

This commit adds the support of JMS annotated endpoint. Can be
activated both by @EnableJms or <jms:annotation-driven/> and
detects methods of managed beans annotated with @JmsListener,
either directly or through a meta-annotation.

Containers are created and managed under the cover by a registry
at application startup time. Container creation is delegated to a
JmsListenerContainerFactory that is identified by the containerFactory
attribute of the JmsListener annotation. Containers can be
retrieved from the registry using a custom id that can be specified
directly on the annotation.

A "factory-id" attribute is available on the container element of
the XML namespace. When it is present, the configuration defined at
the namespace level is used to build a JmsListenerContainerFactory
that is exposed with the value of the "factory-id" attribute. This can
be used as a smooth migration path for users having listener containers
defined at the namespace level. It is also possible to migrate all
listeners to annotated endpoints and yet keep the
<jms:listener-container> or <jms:jca-listener-container> element to
share the container configuration.

The configuration can be fine-tuned by implementing the
JmsListenerConfigurer interface which gives access to the registrar
used to register endpoints. This includes a programmatic registration
of endpoints in complement to the declarative approach. A default
JmsListenerContainerFactory can also be specified to be used if no
containerFactory has been set on the annotation.

Annotated methods can have flexible method arguments that are similar
to what @MessageMapping provides. In particular, jms listener endpoint
methods can fully use the messaging abstraction, including convenient
header accessors. It is also possible to inject the raw
javax.jms.Message and the Session for more advanced use cases. The
payload can be injected as long as the conversion service is able to
convert it from the original type of the JMS payload. By
default, a DefaultJmsHandlerMethodFactory is used but it can be
configured further to support additional method arguments or to
customize conversion and validation support.

The return type of an annotated method can also be an instance of
Spring's Message abstraction. Instead of just converting the payload,
such response type allows to communicate standard and custom headers.

The JmsHeaderMapper infrastructure from Spring integration has also
been migrated to the Spring framework. SimpleJmsHeaderMapper is based
on SI's DefaultJmsHeaderMapper. The simple implementation maps all
JMS headers so that the generated Message abstraction has all the
information stored in the protocol specific message.

Issue: SPR-9882
This commit is contained in:
Stephane Nicoll
2014-03-12 17:43:14 +01:00
parent 6cb9a144db
commit 713dd60fa7
71 changed files with 7983 additions and 525 deletions

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven default-container-factory="defaultFactory"/>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$DefaultBean"/>
<bean id="defaultFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
</beans>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven handler-method-factory="jmsHandlerMethodFactory"/>
<bean id="jmsHandlerMethodFactory" class="org.springframework.jms.config.DefaultJmsHandlerMethodFactory">
<property name="validator">
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$TestValidator"/>
</property>
</bean>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$ValidationBean"/>
<bean id="defaultFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
</beans>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven registry="customRegistry"/>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$CustomBean"/>
<bean id="customRegistry" class="org.springframework.jms.config.JmsListenerEndpointRegistry"/>
<bean id="defaultFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
<bean id="customFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
<bean id="simpleMessageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"/>
<bean class="org.springframework.jms.annotation.AnnotationDrivenNamespaceTests$CustomJmsListenerConfigurer">
<property name="messageListener" ref="simpleMessageListener"/>
<property name="containerFactory" ref="defaultFactory"/>
</bean>
</beans>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven/>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$FullBean"/>
<bean id="simpleFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
</beans>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:annotation-driven/>
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$SampleBean"/>
<bean id="defaultFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
<bean id="simpleFactory" class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
</beans>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
<jms:listener-container factory-id="testJmsFactory"
connection-factory="testConnectionFactory" task-executor="testTaskExecutor"
destination-resolver="testDestinationResolver" message-converter="testMessageConverter"
transaction-manager="testTransactionManager" error-handler="testErrorHandler"
cache="connection" concurrency="3-5" prefetch="50" receive-timeout="100" recovery-interval="1000" phase="99">
<jms:listener id="listener1" destination="testDestination" ref="testBean1" method="setName"/>
<jms:listener id="listener2" destination="testDestination" ref="testBean2" method="setName" response-destination="responseDestination"/>
</jms:listener-container>
<!-- TODO: remove the task-executor reference once issue with blocking on stop is resolved -->
<jms:listener-container task-executor="testTaskExecutor" concurrency="${concurrency}">
<jms:listener destination="testDestination" ref="testBean3"/>
</jms:listener-container>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="properties">
<props>
<prop key="concurrency">2-3</prop>
</props>
</property>
</bean>
<jms:jca-listener-container factory-id="testJcaFactory"
resource-adapter="testResourceAdapter" activation-spec-factory="testActivationSpecFactory"
message-converter="testMessageConverter" concurrency="5" prefetch="50" phase="77">
<jms:listener id="listener3" destination="testDestination" ref="testBean1" method="setName"/>
<jms:listener id="listener4" destination="testDestination" ref="testBean2" method="setName" response-destination="responseDestination"/>
</jms:jca-listener-container>
<jms:jca-listener-container activation-spec-factory="testActivationSpecFactory">
<jms:listener destination="testDestination" ref="testBean3"/>
</jms:jca-listener-container>
<!-- Only export the factory -->
<jms:listener-container factory-id="onlyJmsFactory"
connection-factory="testConnectionFactory" task-executor="testTaskExecutor"
destination-resolver="testDestinationResolver" message-converter="testMessageConverter"
transaction-manager="testTransactionManager" error-handler="testErrorHandler"
concurrency="3-5" prefetch="50" receive-timeout="100" recovery-interval="1000"/>
<!-- the default ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.StubConnectionFactory"/>
<bean id="testConnectionFactory" class="org.springframework.jms.StubConnectionFactory"/>
<!-- the default ResourceAdapter -->
<bean id="resourceAdapter" class="org.springframework.jca.StubResourceAdapter"/>
<bean id="testResourceAdapter" class="org.springframework.jca.StubResourceAdapter"/>
<bean id="testTaskExecutor" class="org.springframework.core.task.StubTaskExecutor"/>
<bean id="testActivationSpecFactory" class="org.springframework.jms.listener.endpoint.StubJmsActivationSpecFactory"/>
<bean id="testDestinationResolver" class="org.springframework.jms.support.destination.DynamicDestinationResolver"/>
<bean id="testMessageConverter" class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
<bean id="testTransactionManager" class="org.springframework.tests.transaction.CallCountingTransactionManager"/>
<bean id="testErrorHandler" class="org.springframework.jms.config.JmsNamespaceHandlerTests$TestErrorHandler"/>
<bean id="testBean1" class="org.springframework.tests.sample.beans.TestBean"/>
<bean id="testBean2" class="org.springframework.tests.sample.beans.TestBean"/>
<bean id="testBean3" class="org.springframework.jms.config.JmsNamespaceHandlerTests$TestMessageListener"/>
<!-- TODO: remove this once issue with blocking on stop is resolved -->
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<property name="timeoutPerShutdownPhase" value="0"/>
</bean>
</beans>