diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AdminParser.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AdminParser.java index 4dc06b13..a6496344 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AdminParser.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AdminParser.java @@ -73,5 +73,7 @@ class AdminParser extends AbstractSingleBeanDefinitionParser { } NamespaceUtils.setValueIfAttributeDefined(builder, element, IGNORE_DECLARATION_EXCEPTIONS); + NamespaceUtils.setValueIfAttributeDefined(builder, element, "explicit-declarations-only"); } + } diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitAdmin.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitAdmin.java index b03c82e4..0db8207e 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitAdmin.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitAdmin.java @@ -138,6 +138,8 @@ public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, Applicat private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); + private boolean explicitDeclarationsOnly; + private volatile boolean running = false; private volatile DeclarationExceptionEvent lastDeclarationExceptionEvent; @@ -429,6 +431,17 @@ public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, Applicat }); } + /** + * Set to true to only declare {@link Declarable} beans that are explicitly configured + * to be declared by this admin. + * @param explicitDeclarationsOnly true to ignore beans with no admin declaration + * configuration. + * @since 2.1.9 + */ + public void setExplicitDeclarationsOnly(boolean explicitDeclarationsOnly) { + this.explicitDeclarationsOnly = explicitDeclarationsOnly; + } + /** * Set a retry template for auto declarations. There is a race condition with * auto-delete, exclusive queues in that the queue might still exist for a short time, @@ -622,12 +635,16 @@ public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, Applicat */ private Collection filterDeclarables(Collection declarables) { return declarables.stream() - .filter(d -> d.shouldDeclare() // NOSONAR boolean complexity - && (d.getDeclaringAdmins().isEmpty() || d.getDeclaringAdmins().contains(this) - || (this.beanName != null && d.getDeclaringAdmins().contains(this.beanName)))) + .filter(dec -> dec.shouldDeclare() && declarableByMe(dec)) .collect(Collectors.toList()); } + private boolean declarableByMe(T dec) { + return (dec.getDeclaringAdmins().isEmpty() && !this.explicitDeclarationsOnly) // NOSONAR boolean complexity + || dec.getDeclaringAdmins().contains(this) + || (this.beanName != null && dec.getDeclaringAdmins().contains(this.beanName)); + } + // private methods for declaring Exchanges, Queues, and Bindings on a Channel private void declareExchanges(final Channel channel, final Exchange... exchanges) throws IOException { diff --git a/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-2.2.xsd b/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-2.2.xsd index 4f35c551..bcf1abb3 100644 --- a/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-2.2.xsd +++ b/spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-2.2.xsd @@ -1069,6 +1069,19 @@ + + + + + + + + diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/AdminParserTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/AdminParserTests.java index 5e10a6d5..f3acbb82 100644 --- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/AdminParserTests.java +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/AdminParserTests.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.utils.test.TestUtils; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; @@ -56,20 +57,28 @@ public final class AdminParserTests { private boolean initialisedWithTemplate; @Test - public void testInvalid() throws Exception { - contextIndex = 1; - validContext = false; - doTest(); + public void testValid0() throws Exception { + this.expectedAutoStartup = true; + this.contextIndex = 0; + this.validContext = true; + doTest(false); } @Test - public void testValid() throws Exception { - contextIndex = 2; - validContext = true; - doTest(); + public void testInvalid1() throws Exception { + this.contextIndex = 1; + this.validContext = false; + doTest(false); } - private void doTest() throws Exception { + @Test + public void testValid2() throws Exception { + this.contextIndex = 2; + this.validContext = true; + doTest(true); + } + + private void doTest(boolean explicit) throws Exception { // Create context DefaultListableBeanFactory beanFactory = loadContext(); if (beanFactory == null) { @@ -79,19 +88,20 @@ public final class AdminParserTests { // Validate values RabbitAdmin admin; - if (StringUtils.hasText(adminBeanName)) { - admin = beanFactory.getBean(adminBeanName, RabbitAdmin.class); + if (StringUtils.hasText(this.adminBeanName)) { + admin = beanFactory.getBean(this.adminBeanName, RabbitAdmin.class); } else { admin = beanFactory.getBean(RabbitAdmin.class); } - assertThat(admin.isAutoStartup()).isEqualTo(expectedAutoStartup); - assertThat(admin.getRabbitTemplate().getConnectionFactory()).isEqualTo(beanFactory.getBean(ConnectionFactory.class)); + assertThat(admin.isAutoStartup()).isEqualTo(this.expectedAutoStartup); + assertThat(admin.getRabbitTemplate().getConnectionFactory()) + .isEqualTo(beanFactory.getBean(ConnectionFactory.class)); - if (initialisedWithTemplate) { + if (this.initialisedWithTemplate) { assertThat(admin.getRabbitTemplate()).isEqualTo(beanFactory.getBean(RabbitTemplate.class)); } - + assertThat(TestUtils.getPropertyValue(admin, "explicitDeclarationsOnly", Boolean.class)).isEqualTo(explicit); } /** @@ -107,12 +117,12 @@ public final class AdminParserTests { beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions(resource); - if (!validContext) { + if (!this.validContext) { fail("Context " + resource + " failed to load"); } } catch (BeanDefinitionParsingException e) { - if (validContext) { + if (this.validContext) { // Context expected to be valid - throw an exception up throw e; } diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitAdminDeclarationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitAdminDeclarationTests.java index cdeb2467..ed09596f 100644 --- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitAdminDeclarationTests.java +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitAdminDeclarationTests.java @@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willReturn; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -261,16 +262,31 @@ public class RabbitAdminDeclarationTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); Config.listener1.onCreate(Config.conn1); verify(Config.channel1).queueDeclare("foo", true, false, false, new HashMap<>()); + verify(Config.channel1, never()).queueDeclare("baz", true, false, false, new HashMap<>()); + verify(Config.channel1).queueDeclare("qux", true, false, false, new HashMap<>()); verify(Config.channel1).exchangeDeclare("bar", "direct", true, false, true, new HashMap()); verify(Config.channel1).queueBind("foo", "bar", "foo", null); Config.listener2.onCreate(Config.conn2); verify(Config.channel2, never()) .queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), isNull()); + verify(Config.channel1, never()).queueDeclare("baz", true, false, false, new HashMap<>()); + verify(Config.channel2).queueDeclare("qux", true, false, false, new HashMap<>()); verify(Config.channel2, never()) .exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(), anyBoolean(), anyMap()); verify(Config.channel2, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), anyMap()); + + Config.listener3.onCreate(Config.conn3); + verify(Config.channel3, never()) + .queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), isNull()); + verify(Config.channel3).queueDeclare("baz", true, false, false, new HashMap<>()); + verify(Config.channel3, never()).queueDeclare("qux", true, false, false, new HashMap<>()); + verify(Config.channel3, never()) + .exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(), + anyBoolean(), anyMap()); + verify(Config.channel3, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), anyMap()); + context.close(); } @@ -334,21 +350,28 @@ public class RabbitAdminDeclarationTests { private static Connection conn2 = mock(Connection.class); + private static Connection conn3 = mock(Connection.class); + private static Channel channel1 = mock(Channel.class); private static Channel channel2 = mock(Channel.class); + private static Channel channel3 = mock(Channel.class); + private static ConnectionListener listener1; private static ConnectionListener listener2; + private static ConnectionListener listener3; + @Bean public ConnectionFactory cf1() throws IOException { ConnectionFactory connectionFactory = mock(ConnectionFactory.class); when(connectionFactory.createConnection()).thenReturn(conn1); when(conn1.createChannel(false)).thenReturn(channel1); - when(channel1.queueDeclare("foo", true, false, false, new HashMap<>())) - .thenReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0)); + willAnswer(inv -> { + return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0); + }).given(channel1).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any()); doAnswer(invocation -> { listener1 = invocation.getArgument(0); return null; @@ -361,8 +384,9 @@ public class RabbitAdminDeclarationTests { ConnectionFactory connectionFactory = mock(ConnectionFactory.class); when(connectionFactory.createConnection()).thenReturn(conn2); when(conn2.createChannel(false)).thenReturn(channel2); - when(channel2.queueDeclare("foo", true, false, false, null)) - .thenReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0)); + willAnswer(inv -> { + return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0); + }).given(channel2).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any()); doAnswer(invocation -> { listener2 = invocation.getArgument(0); return null; @@ -370,27 +394,59 @@ public class RabbitAdminDeclarationTests { return connectionFactory; } + @Bean + public ConnectionFactory cf3() throws IOException { + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + when(connectionFactory.createConnection()).thenReturn(conn3); + when(conn3.createChannel(false)).thenReturn(channel3); + willAnswer(inv -> { + return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0); + }).given(channel3).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any()); + doAnswer(invocation -> { + listener3 = invocation.getArgument(0); + return null; + }).when(connectionFactory).addConnectionListener(any(ConnectionListener.class)); + return connectionFactory; + } + @Bean public RabbitAdmin admin1() throws IOException { RabbitAdmin rabbitAdmin = new RabbitAdmin(cf1()); - rabbitAdmin.afterPropertiesSet(); return rabbitAdmin; } @Bean public RabbitAdmin admin2() throws IOException { RabbitAdmin rabbitAdmin = new RabbitAdmin(cf2()); - rabbitAdmin.afterPropertiesSet(); return rabbitAdmin; } @Bean - public Queue queue() throws IOException { + public RabbitAdmin admin3() throws IOException { + RabbitAdmin rabbitAdmin = new RabbitAdmin(cf3()); + rabbitAdmin.setExplicitDeclarationsOnly(true); + return rabbitAdmin; + } + + @Bean + public Queue queueFoo() throws IOException { Queue queue = new Queue("foo"); queue.setAdminsThatShouldDeclare(admin1()); return queue; } + @Bean + public Queue queueBaz() throws IOException { + Queue queue = new Queue("baz"); + queue.setAdminsThatShouldDeclare(admin3()); + return queue; + } + + @Bean + public Queue queueQux() { + return new Queue("qux"); + } + @Bean public Exchange exchange() throws IOException { DirectExchange exchange = new DirectExchange("bar"); diff --git a/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/AdminParserTests-2-context.xml b/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/AdminParserTests-2-context.xml index 1cdaf0b2..ce9b0c3c 100644 --- a/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/AdminParserTests-2-context.xml +++ b/spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/AdminParserTests-2-context.xml @@ -8,6 +8,7 @@ - + diff --git a/src/reference/asciidoc/amqp.adoc b/src/reference/asciidoc/amqp.adoc index 8af7732b..4458908d 100644 --- a/src/reference/asciidoc/amqp.adoc +++ b/src/reference/asciidoc/amqp.adoc @@ -4611,6 +4611,8 @@ public SimpleMessageListenerContainer container(ConnectionFactory connectionFact By default, all queues, exchanges, and bindings are declared by all `RabbitAdmin` instances (assuming they have `auto-startup="true"`) in the application context. +Starting with version 2.1.9, the `RabbitAdmin` has a new property `explicitDeclarationsOnly` (which is `false` by default); when this is set to `true`, the admin will only declare beans that are explicitly configured to be declared by that admin. + NOTE: Starting with the 1.2 release, you can conditionally declare these elements. This is particularly useful when an application connects to multiple brokers and needs to specify with which brokers a particular element should be declared. @@ -4626,13 +4628,15 @@ The properties are available as attributes in the namespace, as shown in the fol - + - + + + - + @@ -4642,7 +4646,7 @@ The properties are available as attributes in the namespace, as shown in the fol ---- ==== -NOTE: By default, the `auto-declare` attribute is `true` and, if the `declared-by` is not supplied (or is empty), then all `RabbitAdmin` instances declare the object (as long as the admin's `auto-startup` attribute is `true`, the default). +NOTE: By default, the `auto-declare` attribute is `true` and, if the `declared-by` is not supplied (or is empty), then all `RabbitAdmin` instances declare the object (as long as the admin's `auto-startup` attribute is `true`, the default, and the admin's `explicit-declarations-only` attribute is false). Similarly, you can use Java-based `@Configuration` to achieve the same effect. In the following example, the components are declared by `admin1` but not by`admin2`: diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 9757dadf..0b888137 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -75,6 +75,9 @@ See <> for more information. The `ExchangeBuilder` and `QueueBuilder` fluent APIs used to create `Exchange` and `Queue` objects for declaration by `RabbitAdmin` now support "well known" arguments. See <> for more information. +The `RabbitAdmin` has a new property `explicitDeclarationsOnly`. +See <> for more information. + ===== Connection Factory Changes The `CachingConnectionFactory` has a new property `shuffleAddresses`.