From 9c4c590c081ef4f0ea818b172740a019d8b5a45f Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 27 Feb 2015 12:09:11 +0000 Subject: [PATCH] Add @BusConnectionFactory to qualify the preferred instance If you are using AMQP there needs to be a ConnectionFactory (from Spring Rabbit) in the application context. If there is a single ConnectionFactory it will be used, or if there is a one qualified as @BusConnectionFactory it will be preferred over others, otherwise the @Primary one will be used. If there are multiple unqualified connection factories there will be an error. Fixes gh-13 --- docs/src/main/asciidoc/spring-cloud-bus.adoc | 14 +++ .../bus/amqp/AmqpBusAutoConfiguration.java | 56 +++++++++--- .../cloud/bus/amqp/BusConnectionFactory.java | 40 +++++++++ .../amqp/AmqpBusAutoConfigurationTests.java | 88 +++++++++++++++++-- 4 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 spring-cloud-bus/src/main/java/org/springframework/cloud/bus/amqp/BusConnectionFactory.java diff --git a/docs/src/main/asciidoc/spring-cloud-bus.adoc b/docs/src/main/asciidoc/spring-cloud-bus.adoc index 6011879..15a36a2 100644 --- a/docs/src/main/asciidoc/spring-cloud-bus.adoc +++ b/docs/src/main/asciidoc/spring-cloud-bus.adoc @@ -6,3 +6,17 @@ include::intro.adoc[] == Quick Start include::quickstart.adoc[] + +== Customizing the AMQP ConnectionFactory + +If you are using AMQP there needs to be a `ConnectionFactory` (from +Spring Rabbit) in the application context. If there is a single +`ConnectionFactory` it will be used, or if there is a one qualified as +`@BusConnectionFactory` it will be preferred over others, otherwise +the `@Primary` one will be used. If there are multiple unqualified +connection factories there will be an error. + +Note that Spring Boot (as of 1.2.2) creates a `ConnectionFactory` that +is _not_ `@Primary`, so if you want to use one connection factory for +the bus and another for business messages, you need to create both, +and annotate them `@BusConnectionFactory` and `@Primary` respectively. \ No newline at end of file diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfiguration.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfiguration.java index e1c5d5c..406f7f2 100644 --- a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfiguration.java +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfiguration.java @@ -1,8 +1,11 @@ package org.springframework.cloud.bus.amqp; -import javax.annotation.PostConstruct; - -import org.springframework.amqp.core.*; +import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.amqp.core.AnonymousQueue; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.FanoutExchange; +import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; @@ -21,29 +24,49 @@ import org.springframework.messaging.MessageChannel; import com.fasterxml.jackson.databind.ObjectMapper; /** + * Autoconfiguration for a Spring Cloud Bus on AMQP. Enabled by default if spring-rabbit + * is on the classpath, and can be switched off with + * spring.cloud.bus.amqp.enabled. If there is a single + * {@link ConnectionFactory} in the context it will be used, or if there is a one + * qualified as @BusConnectionFactory it will be preferred over others, + * otherwise the @Primary one will be used. If there are multiple unqualified + * connection factories there will be an autowiring error. Note that Spring Boot (as of + * 1.2.2) creates a ConnectionFactory that is not @Primary, so if you + * want to use one connection factory for the bus and another for business messages, you + * need to create both, and annotate them @BusConnectionFactory and + * @Primary respectively. + * * @author Spencer Gibb + * @author Dave Syer */ @Configuration @ConditionalOnBusEnabled -@ConditionalOnClass(AmqpTemplate.class) +@ConditionalOnClass({ AmqpTemplate.class, RabbitTemplate.class }) @ConditionalOnProperty(value = "spring.cloud.bus.amqp.enabled", matchIfMissing = true) public class AmqpBusAutoConfiguration { public static final String SPRING_CLOUD_BUS = "spring.cloud.bus"; - @Autowired - private ConnectionFactory connectionFactory; + @Autowired(required = false) + @BusConnectionFactory + private ConnectionFactory busConnectionFactory; + + @Autowired(required = false) + private ConnectionFactory primaryConnectionFactory; - @Autowired private RabbitTemplate amqpTemplate; @Autowired(required = false) private ObjectMapper objectMapper; - @PostConstruct - public void init() { - Jackson2JsonMessageConverter converter = messageConverter(); - amqpTemplate.setMessageConverter(converter); + public RabbitTemplate amqpTemplate() { + if (this.amqpTemplate == null) { + RabbitTemplate amqpTemplate = new RabbitTemplate(connectionFactory()); + Jackson2JsonMessageConverter converter = messageConverter(); + amqpTemplate.setMessageConverter(converter); + this.amqpTemplate = amqpTemplate; + } + return amqpTemplate; } @Bean @@ -68,7 +91,7 @@ public class AmqpBusAutoConfiguration { @Qualifier("cloudBusOutboundChannel") MessageChannel cloudBusOutboundChannel) { return IntegrationFlows .from(cloudBusOutboundChannel) - .handle(Amqp.outboundAdapter(this.amqpTemplate).exchangeName( + .handle(Amqp.outboundAdapter(amqpTemplate()).exchangeName( SPRING_CLOUD_BUS)).get(); } @@ -76,11 +99,18 @@ public class AmqpBusAutoConfiguration { public IntegrationFlow cloudBusAmqpInboundFlow( @Qualifier("cloudBusInboundChannel") MessageChannel cloudBusInboundChannel) { return IntegrationFlows - .from(Amqp.inboundAdapter(connectionFactory, localCloudBusQueue()) + .from(Amqp.inboundAdapter(connectionFactory(), localCloudBusQueue()) .messageConverter(messageConverter())) .channel(cloudBusInboundChannel).get(); } + private ConnectionFactory connectionFactory() { + if (busConnectionFactory != null) { + return busConnectionFactory; + } + return primaryConnectionFactory; + } + private Jackson2JsonMessageConverter messageConverter() { Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(); if (objectMapper != null) { diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/amqp/BusConnectionFactory.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/amqp/BusConnectionFactory.java new file mode 100644 index 0000000..551c437 --- /dev/null +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/amqp/BusConnectionFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013-2015 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.cloud.bus.amqp; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.beans.factory.annotation.Qualifier; + +import com.rabbitmq.client.ConnectionFactory; + +/** + * Annotation to mark a bean as the {@link ConnectionFactory} for Spring Cloud Bus + */ +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Qualifier +public @interface BusConnectionFactory { + +} diff --git a/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfigurationTests.java b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfigurationTests.java index 3256078..e827434 100644 --- a/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfigurationTests.java +++ b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfigurationTests.java @@ -1,16 +1,22 @@ package org.springframework.cloud.bus.amqp; -import org.junit.Test; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; -import org.springframework.boot.test.EnvironmentTestUtils; -import org.springframework.cloud.bus.BusAutoConfiguration; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.springframework.cloud.bus.BusAutoConfiguration.SPRING_CLOUD_BUS_ENABLED; +import org.junit.Test; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.cloud.bus.BusAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + /** * @author Dave Syer */ @@ -24,6 +30,30 @@ public class AmqpBusAutoConfigurationTests { context.close(); } + @Test + public void qualifiedConnectionFactory() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + getConfigClasses(QualifiedConnectionFactory.class)); + assertTrue(context.containsBean("cloudBusExchange")); + context.close(); + } + + @Test + public void unqualifiedConnectionFactory() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + getConfigClasses(UnqualifiedConnectionFactory.class)); + assertTrue(context.containsBean("cloudBusExchange")); + context.close(); + } + + @Test + public void twoConnectionFactories() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + getConfigClasses(TwoConnectionFactories.class)); + assertTrue(context.containsBean("cloudBusExchange")); + context.close(); + } + @Test public void notStartedIfBusDisabled() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @@ -36,8 +66,48 @@ public class AmqpBusAutoConfigurationTests { context.close(); } - private Class[] getConfigClasses() { - return new Class[]{AmqpBusAutoConfiguration.class, RabbitAutoConfiguration.class, + private Class[] getConfigClasses(Class... extras) { + Class[] defaults = new Class[]{AmqpBusAutoConfiguration.class, RabbitAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, BusAutoConfiguration.class}; + Class[] result = new Class[extras.length + defaults.length]; + System.arraycopy(extras, 0, result, 0, extras.length); + System.arraycopy(defaults, 0, result, extras.length, defaults.length); + return result; } + + @Configuration + protected static class UnqualifiedConnectionFactory { + @Bean + public ConnectionFactory rabbitConnectionFactory(RabbitProperties config) { + CachingConnectionFactory factory = new CachingConnectionFactory(); + return factory; + } + } + + @Configuration + protected static class QualifiedConnectionFactory { + @Bean + @BusConnectionFactory + public ConnectionFactory rabbitConnectionFactory(RabbitProperties config) { + CachingConnectionFactory factory = new CachingConnectionFactory(); + return factory; + } + } + + @Configuration + protected static class TwoConnectionFactories { + @Bean + @BusConnectionFactory + public ConnectionFactory busConnectionFactory(RabbitProperties config) { + CachingConnectionFactory factory = new CachingConnectionFactory(); + return factory; + } + @Bean + @Primary + public ConnectionFactory rabbitConnectionFactory(RabbitProperties config) { + CachingConnectionFactory factory = new CachingConnectionFactory(); + return factory; + } + } + }