diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAnnotationDrivenConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAnnotationDrivenConfiguration.java new file mode 100644 index 00000000000..7b8e6d4e041 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAnnotationDrivenConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-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.boot.autoconfigure.amqp; + + +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.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Configuration for Spring AMQP annotation driven endpoints. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +@Configuration +@ConditionalOnClass(EnableRabbit.class) +class RabbitAnnotationDrivenConfiguration { + + @Autowired(required = false) + private PlatformTransactionManager transactionManager; + + @Bean + @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory") + public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory( + ConnectionFactory connectionFactory) { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setConnectionFactory(connectionFactory); + if (this.transactionManager != null) { + factory.setTransactionManager(this.transactionManager); + } + return factory; + } + + @EnableRabbit + @ConditionalOnMissingBean(name = RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME) + protected static class EnableRabbitConfiguration { + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java index fcd9cbf3b26..47f9d38ccec 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -29,6 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import com.rabbitmq.client.Channel; @@ -74,6 +76,7 @@ import com.rabbitmq.client.Channel; @Configuration @ConditionalOnClass({ RabbitTemplate.class, Channel.class }) @EnableConfigurationProperties(RabbitProperties.class) +@Import(RabbitAnnotationDrivenConfiguration.class) public class RabbitAutoConfiguration { @Bean @@ -119,4 +122,15 @@ public class RabbitAutoConfiguration { } + @ConditionalOnClass(RabbitMessagingTemplate.class) + @ConditionalOnMissingBean(RabbitMessagingTemplate.class) + protected static class MessagingTemplateConfiguration { + + @Bean + public RabbitMessagingTemplate jmsMessagingTemplate(RabbitTemplate rabbitTemplate) { + return new RabbitMessagingTemplate(rabbitTemplate); + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java index 32418a32603..973bb04dffa 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java @@ -19,19 +19,26 @@ package org.springframework.boot.autoconfigure.amqp; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; + import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.rabbit.annotation.EnableRabbit; +import org.springframework.amqp.rabbit.config.RabbitListenerConfigUtils; +import org.springframework.amqp.rabbit.config.RabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; /** * Tests for {@link RabbitAutoConfiguration}. @@ -46,30 +53,28 @@ public class RabbitAutoConfigurationTests { public ExpectedException thrown = ExpectedException.none(); @Test - public void testDefaultRabbitTemplate() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, RabbitAutoConfiguration.class); - this.context.refresh(); + public void testDefaultRabbitConfiguration() { + load(TestConfiguration.class); RabbitTemplate rabbitTemplate = this.context.getBean(RabbitTemplate.class); + RabbitMessagingTemplate messagingTemplate = this.context + .getBean(RabbitMessagingTemplate.class); CachingConnectionFactory connectionFactory = this.context .getBean(CachingConnectionFactory.class); RabbitAdmin amqpAdmin = this.context.getBean(RabbitAdmin.class); - assertNotNull(rabbitTemplate); - assertNotNull(connectionFactory); + assertEquals(connectionFactory, rabbitTemplate.getConnectionFactory()); + assertEquals(rabbitTemplate, messagingTemplate.getRabbitTemplate()); assertNotNull(amqpAdmin); - assertEquals(rabbitTemplate.getConnectionFactory(), connectionFactory); assertEquals("localhost", connectionFactory.getHost()); + assertTrue("Listener container factory should be created by default", + this.context.containsBean("rabbitListenerContainerFactory")); } @Test public void testRabbitTemplateWithOverrides() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, RabbitAutoConfiguration.class); - EnvironmentTestUtils.addEnvironment(this.context, + load(TestConfiguration.class, "spring.rabbitmq.host:remote-server", "spring.rabbitmq.port:9000", "spring.rabbitmq.username:alice", "spring.rabbitmq.password:secret", "spring.rabbitmq.virtual_host:/vhost"); - this.context.refresh(); CachingConnectionFactory connectionFactory = this.context .getBean(CachingConnectionFactory.class); assertEquals("remote-server", connectionFactory.getHost()); @@ -79,11 +84,7 @@ public class RabbitAutoConfigurationTests { @Test public void testRabbitTemplateEmptyVirtualHost() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, RabbitAutoConfiguration.class); - EnvironmentTestUtils - .addEnvironment(this.context, "spring.rabbitmq.virtual_host:"); - this.context.refresh(); + load(TestConfiguration.class, "spring.rabbitmq.virtual_host:"); CachingConnectionFactory connectionFactory = this.context .getBean(CachingConnectionFactory.class); assertEquals("/", connectionFactory.getVirtualHost()); @@ -91,11 +92,7 @@ public class RabbitAutoConfigurationTests { @Test public void testRabbitTemplateVirtualHostNoLeadingSlash() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, RabbitAutoConfiguration.class); - EnvironmentTestUtils.addEnvironment(this.context, - "spring.rabbitmq.virtual_host:foo"); - this.context.refresh(); + load(TestConfiguration.class, "spring.rabbitmq.virtual_host:foo"); CachingConnectionFactory connectionFactory = this.context .getBean(CachingConnectionFactory.class); assertEquals("foo", connectionFactory.getVirtualHost()); @@ -103,11 +100,7 @@ public class RabbitAutoConfigurationTests { @Test public void testRabbitTemplateVirtualHostMultiLeadingSlashes() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, RabbitAutoConfiguration.class); - EnvironmentTestUtils.addEnvironment(this.context, - "spring.rabbitmq.virtual_host:///foo"); - this.context.refresh(); + load(TestConfiguration.class, "spring.rabbitmq.virtual_host:///foo"); CachingConnectionFactory connectionFactory = this.context .getBean(CachingConnectionFactory.class); assertEquals("///foo", connectionFactory.getVirtualHost()); @@ -115,21 +108,15 @@ public class RabbitAutoConfigurationTests { @Test public void testRabbitTemplateDefaultVirtualHost() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, RabbitAutoConfiguration.class); - EnvironmentTestUtils.addEnvironment(this.context, - "spring.rabbitmq.virtual_host:/"); - this.context.refresh(); + load(TestConfiguration.class, "spring.rabbitmq.virtual_host:/"); CachingConnectionFactory connectionFactory = this.context .getBean(CachingConnectionFactory.class); assertEquals("/", connectionFactory.getVirtualHost()); } @Test - public void testConnectionFactoryBackoff() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration2.class, RabbitAutoConfiguration.class); - this.context.refresh(); + public void testConnectionFactoryBackOff() { + load(TestConfiguration2.class); RabbitTemplate rabbitTemplate = this.context.getBean(RabbitTemplate.class); CachingConnectionFactory connectionFactory = this.context .getBean(CachingConnectionFactory.class); @@ -138,13 +125,23 @@ public class RabbitAutoConfigurationTests { assertEquals(8001, connectionFactory.getPort()); } + @Test + public void testRabbitTemplateBackOff() { + load(TestConfiguration3.class); + RabbitTemplate rabbitTemplate = this.context.getBean(RabbitTemplate.class); + assertEquals(this.context.getBean("testMessageConverter"), rabbitTemplate.getMessageConverter()); + } + + @Test + public void testRabbitMessagingTemplateBackOff() { + load(TestConfiguration4.class); + RabbitMessagingTemplate messagingTemplate = this.context.getBean(RabbitMessagingTemplate.class); + assertEquals("fooBar", messagingTemplate.getDefaultDestination()); + } + @Test public void testStaticQueues() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(TestConfiguration.class, RabbitAutoConfiguration.class); - EnvironmentTestUtils - .addEnvironment(this.context, "spring.rabbitmq.dynamic:false"); - this.context.refresh(); + load(TestConfiguration.class, "spring.rabbitmq.dynamic:false"); // There should NOT be an AmqpAdmin bean when dynamic is switch to false this.thrown.expect(NoSuchBeanDefinitionException.class); this.thrown.expectMessage("No qualifying bean of type " @@ -152,6 +149,46 @@ public class RabbitAutoConfigurationTests { this.context.getBean(AmqpAdmin.class); } + @Test + public void testEnableRabbitCreateDefaultContainerFactory() { + load(EnableRabbitConfiguration.class); + RabbitListenerContainerFactory rabbitListenerContainerFactory = this.context + .getBean("rabbitListenerContainerFactory", RabbitListenerContainerFactory.class); + assertEquals(SimpleRabbitListenerContainerFactory.class, + rabbitListenerContainerFactory.getClass()); + } + + @Test + public void testRabbitListenerContainerFactoryBackOff() { + load(TestConfiguration5.class); + SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory = this.context + .getBean("rabbitListenerContainerFactory", SimpleRabbitListenerContainerFactory.class); + rabbitListenerContainerFactory.setTxSize(10); + verify(rabbitListenerContainerFactory).setTxSize(10); + } + + @Test + public void enableRabbitAutomatically() throws Exception { + load(NoEnableRabbitConfiguration.class); + AnnotationConfigApplicationContext ctx = this.context; + ctx.getBean(RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME); + ctx.getBean(RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME); + } + + private void load(Class config, String... environment) { + this.context = doLoad(new Class[] {config}, environment); + } + + private AnnotationConfigApplicationContext doLoad(Class[] configs, + String... environment) { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.register(configs); + applicationContext.register(RabbitAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(applicationContext, environment); + applicationContext.refresh(); + return applicationContext; + } + @Configuration protected static class TestConfiguration { @@ -164,4 +201,52 @@ public class RabbitAutoConfigurationTests { return new CachingConnectionFactory("otherserver", 8001); } } + + @Configuration + protected static class TestConfiguration3 { + + @Bean + RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMessageConverter(testMessageConverter()); + return rabbitTemplate; + } + + @Bean + public MessageConverter testMessageConverter() { + return mock(MessageConverter.class); + } + + } + + @Configuration + protected static class TestConfiguration4 { + + @Bean + RabbitMessagingTemplate messagingTemplate(RabbitTemplate rabbitTemplate) { + RabbitMessagingTemplate messagingTemplate = new RabbitMessagingTemplate(rabbitTemplate); + messagingTemplate.setDefaultDestination("fooBar"); + return messagingTemplate; + } + } + + @Configuration + protected static class TestConfiguration5 { + + @Bean + RabbitListenerContainerFactory rabbitListenerContainerFactory() { + return mock(SimpleRabbitListenerContainerFactory.class); + } + + } + + @Configuration + @EnableRabbit + protected static class EnableRabbitConfiguration { + } + + @Configuration + protected static class NoEnableRabbitConfiguration { + } + }