Allow to easily customize ListenerContainerFactory

Previously, if one wants to create a custom `JmsListenerContainerFactory`
or `RabbitListenerContainerFactory`, a bunch of code from the auto-
configuration must be duplicated.

This commit introduces two services to configure such factory for JMS
and AMQP with the same sensible defaults that were applied by the
auto-configufrations.

Closes gh-5138
This commit is contained in:
Stephane Nicoll 2016-02-12 13:41:35 +01:00
parent 1c170b35ea
commit b726974bca
6 changed files with 327 additions and 57 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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.
@ -20,7 +20,6 @@ import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.config.RabbitListenerConfigUtils;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties.Listener;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@ -37,30 +36,18 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {
@Bean
@ConditionalOnMissingBean
public RabbitListenerContainerFactoryConfigurer rabbitListenerContainerFactoryConfigurer() {
return new RabbitListenerContainerFactoryConfigurer();
}
@Bean
@ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory connectionFactory, RabbitProperties config) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
Listener listenerConfig = config.getListener();
factory.setAutoStartup(listenerConfig.isAutoStartup());
if (listenerConfig.getAcknowledgeMode() != null) {
factory.setAcknowledgeMode(listenerConfig.getAcknowledgeMode());
}
if (listenerConfig.getConcurrency() != null) {
factory.setConcurrentConsumers(listenerConfig.getConcurrency());
}
if (listenerConfig.getMaxConcurrency() != null) {
factory.setMaxConcurrentConsumers(listenerConfig.getMaxConcurrency());
}
if (listenerConfig.getPrefetch() != null) {
factory.setPrefetchCount(listenerConfig.getPrefetch());
}
if (listenerConfig.getTransactionSize() != null) {
factory.setTxSize(listenerConfig.getTransactionSize());
}
return factory;
RabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory) {
return configurer.createRabbitListenerContainerFactory(connectionFactory);
}
@EnableRabbit

View File

@ -0,0 +1,87 @@
/*
* Copyright 2012-2016 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.boot.autoconfigure.amqp;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
/**
* Configure {@link RabbitListenerContainerFactory} with sensible defaults.
*
* @author Stephane Nicoll
* @since 1.3.3
*/
public final class RabbitListenerContainerFactoryConfigurer {
private RabbitProperties rabbitProperties;
/**
* Set the {@link RabbitProperties} to use.
* @param rabbitProperties the {@link RabbitProperties}
*/
@Autowired
public void setRabbitProperties(RabbitProperties rabbitProperties) {
this.rabbitProperties = rabbitProperties;
}
/**
* Create a new and pre-configured {@link SimpleRabbitListenerContainerFactory} instance
* for the specified {@link ConnectionFactory}.
* @param connectionFactory the {@link ConnectionFactory} to use.
* @return a pre-configured {@link SimpleRabbitListenerContainerFactory}
*/
public SimpleRabbitListenerContainerFactory createRabbitListenerContainerFactory(
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configure(factory, connectionFactory);
return factory;
}
/**
* Apply the default settings for the specified jms listener container factory. The
* factory can be further tuned and default settings can be overridden.
* @param factory the {@link SimpleRabbitListenerContainerFactory} instance to configure
* @param connectionFactory the {@link ConnectionFactory} to use
*/
public void configure(SimpleRabbitListenerContainerFactory factory,
ConnectionFactory connectionFactory) {
Assert.notNull(factory, "Factory must not be null");
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
factory.setConnectionFactory(connectionFactory);
RabbitProperties.Listener listenerConfig = this.rabbitProperties.getListener();
factory.setAutoStartup(listenerConfig.isAutoStartup());
if (listenerConfig.getAcknowledgeMode() != null) {
factory.setAcknowledgeMode(listenerConfig.getAcknowledgeMode());
}
if (listenerConfig.getConcurrency() != null) {
factory.setConcurrentConsumers(listenerConfig.getConcurrency());
}
if (listenerConfig.getMaxConcurrency() != null) {
factory.setMaxConcurrentConsumers(listenerConfig.getMaxConcurrency());
}
if (listenerConfig.getPrefetch() != null) {
factory.setPrefetchCount(listenerConfig.getPrefetch());
}
if (listenerConfig.getTransactionSize() != null) {
factory.setTxSize(listenerConfig.getTransactionSize());
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 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.
@ -18,7 +18,6 @@ package org.springframework.boot.autoconfigure.jms;
import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -29,7 +28,6 @@ import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerConfigUtils;
import org.springframework.jms.support.destination.DestinationResolver;
import org.springframework.jms.support.destination.JndiDestinationResolver;
import org.springframework.transaction.jta.JtaTransactionManager;
/**
* Configuration for Spring 4.1 annotation driven JMS.
@ -42,41 +40,17 @@ import org.springframework.transaction.jta.JtaTransactionManager;
@ConditionalOnClass(EnableJms.class)
class JmsAnnotationDrivenConfiguration {
@Autowired(required = false)
private DestinationResolver destinationResolver;
@Autowired(required = false)
private JtaTransactionManager transactionManager;
@Autowired
private JmsProperties properties;
@Bean
@ConditionalOnMissingBean
public JmsListenerContainerFactoryConfigurer jmsListenerContainerFactoryConfigurer() {
return new JmsListenerContainerFactoryConfigurer();
}
@Bean
@ConditionalOnMissingBean(name = "jmsListenerContainerFactory")
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(
ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(this.properties.isPubSubDomain());
if (this.transactionManager != null) {
factory.setTransactionManager(this.transactionManager);
}
else {
factory.setSessionTransacted(true);
}
if (this.destinationResolver != null) {
factory.setDestinationResolver(this.destinationResolver);
}
JmsProperties.Listener listener = this.properties.getListener();
factory.setAutoStartup(listener.isAutoStartup());
if (listener.getAcknowledgeMode() != null) {
factory.setSessionAcknowledgeMode(listener.getAcknowledgeMode().getMode());
}
String concurrency = listener.formatConcurrency();
if (concurrency != null) {
factory.setConcurrency(concurrency);
}
return factory;
JmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
return configurer.createJmsListenerContainerFactory(connectionFactory);
}
@EnableJms

View File

@ -0,0 +1,116 @@
/*
* Copyright 2012-2016 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.boot.autoconfigure.jms;
import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.support.destination.DestinationResolver;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.util.Assert;
/**
* Configure {@link JmsListenerContainerFactory} with sensible defaults.
*
* @author Stephane Nicoll
* @since 1.3.3
*/
public final class JmsListenerContainerFactoryConfigurer {
private DestinationResolver destinationResolver;
private JtaTransactionManager transactionManager;
private JmsProperties jmsProperties;
/**
* Set the {@link DestinationResolver} to use or {@code null} if no destination
* resolver should be associated with the factory by default.
* @param destinationResolver the {@link DestinationResolver}
*/
@Autowired(required = false)
public void setDestinationResolver(DestinationResolver destinationResolver) {
this.destinationResolver = destinationResolver;
}
/**
* Set the {@link JtaTransactionManager} to use or {@code null} if the JTA
* support should not be used.
* @param transactionManager the {@link JtaTransactionManager}
*/
@Autowired(required = false)
public void setTransactionManager(JtaTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* Set the {@link JmsProperties to use}.
* @param jmsProperties the {@link JmsProperties}
*/
@Autowired
public void setJmsProperties(JmsProperties jmsProperties) {
this.jmsProperties = jmsProperties;
}
/**
* Create a new and pre-configured {@link DefaultJmsListenerContainerFactory} instance
* for the specified {@link ConnectionFactory}.
* @param connectionFactory the {@link ConnectionFactory} to use.
* @return a pre-configured {@link DefaultJmsListenerContainerFactory}
*/
public DefaultJmsListenerContainerFactory createJmsListenerContainerFactory(
ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configure(factory, connectionFactory);
return factory;
}
/**
* Apply the default settings for the specified jms listener container factory. The
* factory can be further tuned and default settings can be overridden.
* @param factory the {@link DefaultJmsListenerContainerFactory} instance to configure
* @param connectionFactory the {@link ConnectionFactory} to use
*/
public void configure(DefaultJmsListenerContainerFactory factory,
ConnectionFactory connectionFactory) {
Assert.notNull(factory, "Factory must not be null");
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(this.jmsProperties.isPubSubDomain());
if (this.transactionManager != null) {
factory.setTransactionManager(this.transactionManager);
}
else {
factory.setSessionTransacted(true);
}
if (this.destinationResolver != null) {
factory.setDestinationResolver(this.destinationResolver);
}
JmsProperties.Listener listener = this.jmsProperties.getListener();
factory.setAutoStartup(listener.isAutoStartup());
if (listener.getAcknowledgeMode() != null) {
factory.setSessionAcknowledgeMode(listener.getAcknowledgeMode().getMode());
}
String concurrency = listener.formatConcurrency();
if (concurrency != null) {
factory.setConcurrency(concurrency);
}
}
}

View File

@ -212,6 +212,21 @@ public class JmsAutoConfigurationTests {
.getPropertyValue("transactionManager"));
}
@Test
public void testCustomContainerFactoryWithConfigurer() {
this.context = doLoad(new Class<?>[]{TestConfiguration9.class,
EnableJmsConfiguration.class}, "spring.jms.listener.autoStartup=false");
assertTrue(this.context.containsBean("jmsListenerContainerFactory"));
JmsListenerContainerFactory<?> jmsListenerContainerFactory = this.context.getBean(
"customListenerContainerFactory", JmsListenerContainerFactory.class);
assertEquals(DefaultJmsListenerContainerFactory.class,
jmsListenerContainerFactory.getClass());
DefaultMessageListenerContainer listenerContainer = ((DefaultJmsListenerContainerFactory) jmsListenerContainerFactory)
.createListenerContainer(mock(JmsListenerEndpoint.class));
assertEquals(DefaultMessageListenerContainer.CACHE_CONSUMER, listenerContainer.getCacheLevel());
assertFalse(listenerContainer.isAutoStartup());
}
@Test
public void testPubSubDisabledByDefault() {
load(TestConfiguration.class);
@ -447,6 +462,21 @@ public class JmsAutoConfigurationTests {
}
@Configuration
protected static class TestConfiguration9 {
@Bean
JmsListenerContainerFactory<?> customListenerContainerFactory(
JmsListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = configurer
.createJmsListenerContainerFactory(connectionFactory);
factory.setCacheLevel(DefaultMessageListenerContainer.CACHE_CONSUMER);
return factory;
}
}
@Configuration
@EnableJms
protected static class EnableJmsConfiguration {

View File

@ -3489,6 +3489,44 @@ The following component creates a listener endpoint on the `someQueue` destinati
TIP: Check {spring-javadoc}/jms/annotation/EnableJms.{dc-ext}[the Javadoc of `@EnableJms`] for
more details.
If you need to create more `JmsListenerContainerFactory` instances or if you want to override
the default, Spring Boot provides a `JmsListenerContainerFactoryConfigurer` that you can use
to initialize a `DefaultJmsListenerContainerFactory` with the same settings as the one that
is auto-configured.
For instance, the following exposes another factory that uses a specific `MessageConverter`:
[source,java,indent=0]
----
@Configuration
static class JmsConfiguration {
@Bean
public DefaultJmsListenerContainerFactory myFactory(
JmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = configurer
.createJmsListenerContainerFactory(connectionFactory());
factory.setMessageConverter(myMessageConverter());
return factory;
}
}
----
That you can use in any `@JmsListener`-annotated method as follows:
[source,java,indent=0]
----
@Component
public class MyBean {
@JmsListener(destination = "someQueue", **containerFactory="myFactory"**)
public void processMessage(String content) {
// ...
}
}
----
[[boot-features-amqp]]
@ -3585,6 +3623,44 @@ The following component creates a listener endpoint on the `someQueue` queue:
TIP: Check {spring-amqp-javadoc}/rabbit/annotation/EnableRabbit.{dc-ext}[the Javadoc of `@EnableRabbit`]
for more details.
If you need to create more `RabbitListenerContainerFactory` instances or if you want to override
the default, Spring Boot provides a `RabbitListenerContainerFactoryConfigurer` that you can use
to initialize a `SimpleRabbitListenerContainerFactory` with the same settings as the one that
is auto-configured.
For instance, the following exposes another factory that uses a specific `MessageConverter`:
[source,java,indent=0]
----
@Configuration
static class RabbitConfiguration {
@Bean
public SimpleRabbitListenerContainerFactory myFactory(
RabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = configurer
.createRabbitListenerContainerFactory(connectionFactory());
factory.setMessageConverter(myMessageConverter());
return factory;
}
}
----
That you can use in any `@RabbitListener`-annotated method as follows:
[source,java,indent=0]
----
@Component
public class MyBean {
@RabbitListener(queues = "someQueue", **containerFactory="myFactory"**)
public void processMessage(String content) {
// ...
}
}
----
[[boot-features-email]]