Skip to content

Commit

Permalink
JMS annotation-driven endpoints.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
snicoll committed Apr 17, 2014
1 parent 6cb9a14 commit 713dd60
Show file tree
Hide file tree
Showing 71 changed files with 7,915 additions and 457 deletions.
1 change: 1 addition & 0 deletions build.gradle
Expand Up @@ -485,6 +485,7 @@ project("spring-jms") {
compile(project(":spring-beans"))
compile(project(":spring-aop"))
compile(project(":spring-context"))
compile(project(":spring-messaging"))
compile(project(":spring-tx"))
provided("javax.jms:jms-api:1.1-rev-1")
optional(project(":spring-oxm"))
Expand Down
Expand Up @@ -190,6 +190,18 @@ public class AnnotationConfigUtils {
public static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.cache.aspectj.AspectJJCacheConfiguration";

/**
* The bean name of the internally managed jms listener annotation processor.
*/
public static final String JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME =
"org.springframework.jms.config.internalJmsListenerAnnotationProcessor";

/**
* The bean name of the internally managed jms listener endpoint registry.
*/
public static final String JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME =
"org.springframework.jms.config.internalJmsListenerEndpointRegistry";

/**
* The bean name of the internally managed JPA annotation processor.
*/
Expand Down
@@ -0,0 +1,260 @@
/*
* Copyright 2002-2014 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.jms.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;

/**
* Enable JMS listener annotated endpoints that are created under the cover
* by a {@link org.springframework.jms.config.JmsListenerContainerFactory
* JmsListenerContainerFactory}. To be used on
* @{@link org.springframework.context.annotation.Configuration Configuration} classes
* as follows:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig {
* &#064;Bean
* public DefaultJmsListenerContainerFactory myJmsListenerContainerFactory() {
* DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
* factory.setConnectionFactory(connectionFactory());
* factory.setDestinationResolver(destinationResolver());
* factory.setConcurrency("5");
* return factory;
* }
* // other &#064;Bean definitions
* }</pre>
*
* The {@code JmsListenerContainerFactory} is responsible to create the listener container
* responsible for a particular endpoint. Typical implementations, as the
* {@link org.springframework.jms.config.DefaultJmsListenerContainerFactory DefaultJmsListenerContainerFactory}
* used in the sample above, provides the necessary configuration options that are supported by
* the underlying {@link org.springframework.jms.listener.MessageListenerContainer MessageListenerContainer}.
*
* <p>{@code @EnableJms} enables detection of @{@link JmsListener} annotations on
* any Spring-managed bean in the container. For example, given a class {@code MyService}
*
* <pre class="code">
* package com.acme.foo;
*
* public class MyService {
* &#064;JmsListener(containerFactory = "myJmsListenerContainerFactory", destination="myQueue")
* public void process(String msg) {
* // process incoming message
* }
* }</pre>
*
* The container factory to use is identified by the {@link JmsListener#containerFactory() containerFactory}
* attribute defining the name of the {@code JmsListenerContainerFactory} bean to use.
*
* <p>the following configuration would ensure that every time a {@link javax.jms.Message}
* is received on the {@link javax.jms.Destination} named "myQueue", {@code MyService.process()}
* is called with the content of the message:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig {
* &#064;Bean
* public MyService myService() {
* return new MyService();
* }
*
* // JMS infrastructure setup
* }</pre>
*
* Alternatively, if {@code MyService} were annotated with {@code @Component}, the
* following configuration would ensure that its {@code @JmsListener} annotated
* method is invoked with a matching incoming message:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* &#064;ComponentScan(basePackages="com.acme.foo")
* public class AppConfig {
* }</pre>
*
* Note that the created containers are not registered against the application context
* but can be easily located for management purposes using the
* {@link org.springframework.jms.config.JmsListenerEndpointRegistry JmsListenerEndpointRegistry}.
*
* <p>Annotated methods can use flexible signature; in particular, it is possible to use
* the {@link org.springframework.messaging.Message Message} abstraction and related annotations,
* see @{@link JmsListener} Javadoc for more details. For instance, the following would
* inject the content of the message and a a custom "myCounter" JMS header:
*
* <pre class="code">
* &#064;JmsListener(containerFactory = "myJmsListenerContainerFactory", destination="myQueue")
* public void process(String msg, @Header("myCounter") int counter) {
* // process incoming message
* }</pre>
*
* These features are abstracted by the {@link org.springframework.jms.config.JmsHandlerMethodFactory
* JmsHandlerMethodFactory} that is responsible to build the necessary invoker to process
* the annotated method. By default, {@link org.springframework.jms.config.DefaultJmsHandlerMethodFactory
* DefaultJmsHandlerMethodFactory} is used.
*
* <p>When more control is desired, a {@code @Configuration} class may implement
* {@link JmsListenerConfigurer}. This allows access to the underlying
* {@link org.springframework.jms.config.JmsListenerEndpointRegistrar JmsListenerEndpointRegistrar}
* instance. The following example demonstrates how to specify a default
* {@code JmsListenerContainerFactory} so that {@link JmsListener#containerFactory()} may be
* omitted for endpoints willing to use the <em>default</em> container factory.
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig implements JmsListenerConfigurer {
* &#064;Override
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
* registrar.setDefaultContainerFactory(myJmsListenerContainerFactory());
* }
*
* &#064;Bean
* public JmsListenerContainerFactory<?> myJmsListenerContainerFactory() {
* // factory settings
* }
*
* &#064;Bean
* public MyService myService() {
* return new MyService();
* }
* }</pre>
*
* For reference, the example above can be compared to the following Spring XML
* configuration:
* <pre class="code">
* {@code <beans>
* <jms:annotation-driven default-container-factory="myJmsListenerContainerFactory"/>
*
* <bean id="myJmsListenerContainerFactory"
* class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
* // factory settings
* </bean>
*
* <bean id="myService" class="com.acme.foo.MyService"/>
* </beans>
* }</pre>
*
* It is also possible to specify a custom {@link org.springframework.jms.config.JmsListenerEndpointRegistry
* JmsListenerEndpointRegistry} in case you need more control on the way the containers
* are created and managed. The example below also demonstrates how to customize the
* {@code JmsHandlerMethodFactory} to use with a custom {@link org.springframework.validation.Validator
* Validator} so that payloads annotated with {@link org.springframework.validation.annotation.Validated
* @Validated} are first validated against a custom {@code Validator}.
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig implements JmsListenerConfigurer {
* &#064;Override
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
* registrar.setEndpointRegistry(myJmsListenerEndpointRegistry());
* registrar.setJmsHandlerMethodFactory(myJmsHandlerMethodFactory);
* }
*
* &#064;Bean
* public JmsListenerEndpointRegistry<?> myJmsListenerEndpointRegistry() {
* // registry configuration
* }
*
* &#064;Bean
* public JmsHandlerMethodFactory myJmsHandlerMethodFactory() {
* DefaultJmsHandlerMethodFactory factory = new DefaultJmsHandlerMethodFactory();
* factory.setValidator(new MyValidator());
* return factory;
* }
*
* &#064;Bean
* public MyService myService() {
* return new MyService();
* }
* }</pre>
*
* For reference, the example above can be compared to the following Spring XML
* configuration:
* <pre class="code">
* {@code <beans>
* <jms:annotation-driven registry="myJmsListenerEndpointRegistry"
* handler-method-factory="myJmsHandlerMethodFactory"/&gt;
*
* <bean id="myJmsListenerEndpointRegistry"
* class="org.springframework.jms.config.JmsListenerEndpointRegistry">
* // registry configuration
* </bean>
*
* <bean id="myJmsHandlerMethodFactory"
* class="org.springframework.jms.config.DefaultJmsHandlerMethodFactory">
* <property name="validator" ref="myValidator"/>
* </bean>
*
* <bean id="myService" class="com.acme.foo.MyService"/>
* </beans>
* }</pre>
*
* Implementing {@code JmsListenerConfigurer} also allows for fine-grained
* control over endpoints registration via the {@code JmsListenerEndpointRegistrar}.
* For example, the following configures an extra endpoint:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableJms
* public class AppConfig implements JmsListenerConfigurer {
* &#064;Override
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
* SimpleJmsListenerEndpoint myEndpoint = new SimpleJmsListenerEndpoint();
* // ... configure the endpoint
* registrar.registerEndpoint(endpoint, anotherJmsListenerContainerFactory());
* }
*
* &#064;Bean
* public MyService myService() {
* return new MyService();
* }
*
* &#064;Bean
* public JmsListenerContainerFactory<?> anotherJmsListenerContainerFactory() {
* // ...
* }
*
* // JMS infrastructure setup
* }</pre>
*
* Note that all beans implementing {@code JmsListenerConfigurer} will be detected and
* invoked in a similar fashion. The example above can be translated in a regular bean
* definition registered in the context in case you use the XML configuration.
*
* @author Stephane Nicoll
* @since 4.1
* @see JmsListener
* @see JmsListenerAnnotationBeanPostProcessor
* @see org.springframework.jms.config.JmsListenerEndpointRegistrar
* @see org.springframework.jms.config.JmsListenerEndpointRegistry
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(JmsBootstrapConfiguration.class)
public @interface EnableJms {
}
@@ -0,0 +1,54 @@
/*
* Copyright 2002-2014 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.jms.annotation;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.jms.config.JmsListenerEndpointRegistry;

/**
* {@code @Configuration} class that registers a {@link JmsListenerAnnotationBeanPostProcessor}
* bean capable of processing Spring's @{@link JmsListener} annotation. Also register
* a default {@link JmsListenerEndpointRegistry}.
*
* <p>This configuration class is automatically imported when using the @{@link EnableJms}
* annotation. See {@link EnableJms} Javadoc for complete usage.
*
* @author Stephane Nicoll
* @since 4.1
* @see JmsListenerAnnotationBeanPostProcessor
* @see JmsListenerEndpointRegistry
* @see EnableJms
*/
@Configuration
public class JmsBootstrapConfiguration {

@Bean(name = AnnotationConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public JmsListenerAnnotationBeanPostProcessor jmsListenerAnnotationProcessor() {
return new JmsListenerAnnotationBeanPostProcessor();
}

@Bean(name = AnnotationConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)
public JmsListenerEndpointRegistry defaultJmsListenerEndpointRegistry() {
return new JmsListenerEndpointRegistry();
}

}

0 comments on commit 713dd60

Please sign in to comment.