Skip to content

Commit accd202

Browse files
garyrussellartembilan
authored andcommitted
GH-1074: Admin explicitDeclarationsOnly Property
Resolves #1074 Cherry-pick to 2.1.x
1 parent 4b19b61 commit accd202

File tree

8 files changed

+138
-32
lines changed

8 files changed

+138
-32
lines changed

spring-rabbit/src/main/java/org/springframework/amqp/rabbit/config/AdminParser.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
7373
}
7474

7575
NamespaceUtils.setValueIfAttributeDefined(builder, element, IGNORE_DECLARATION_EXCEPTIONS);
76+
NamespaceUtils.setValueIfAttributeDefined(builder, element, "explicit-declarations-only");
7677
}
78+
7779
}

spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitAdmin.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, Applicat
138138

139139
private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
140140

141+
private boolean explicitDeclarationsOnly;
142+
141143
private volatile boolean running = false;
142144

143145
private volatile DeclarationExceptionEvent lastDeclarationExceptionEvent;
@@ -429,6 +431,17 @@ public QueueInformation getQueueInfo(String queueName) {
429431
});
430432
}
431433

434+
/**
435+
* Set to true to only declare {@link Declarable} beans that are explicitly configured
436+
* to be declared by this admin.
437+
* @param explicitDeclarationsOnly true to ignore beans with no admin declaration
438+
* configuration.
439+
* @since 2.1.9
440+
*/
441+
public void setExplicitDeclarationsOnly(boolean explicitDeclarationsOnly) {
442+
this.explicitDeclarationsOnly = explicitDeclarationsOnly;
443+
}
444+
432445
/**
433446
* Set a retry template for auto declarations. There is a race condition with
434447
* auto-delete, exclusive queues in that the queue might still exist for a short time,
@@ -622,12 +635,16 @@ else if (declarable instanceof Binding) {
622635
*/
623636
private <T extends Declarable> Collection<T> filterDeclarables(Collection<T> declarables) {
624637
return declarables.stream()
625-
.filter(d -> d.shouldDeclare() // NOSONAR boolean complexity
626-
&& (d.getDeclaringAdmins().isEmpty() || d.getDeclaringAdmins().contains(this)
627-
|| (this.beanName != null && d.getDeclaringAdmins().contains(this.beanName))))
638+
.filter(dec -> dec.shouldDeclare() && declarableByMe(dec))
628639
.collect(Collectors.toList());
629640
}
630641

642+
private <T extends Declarable> boolean declarableByMe(T dec) {
643+
return (dec.getDeclaringAdmins().isEmpty() && !this.explicitDeclarationsOnly) // NOSONAR boolean complexity
644+
|| dec.getDeclaringAdmins().contains(this)
645+
|| (this.beanName != null && dec.getDeclaringAdmins().contains(this.beanName));
646+
}
647+
631648
// private methods for declaring Exchanges, Queues, and Bindings on a Channel
632649

633650
private void declareExchanges(final Channel channel, final Exchange... exchanges) throws IOException {

spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-2.2.xsd

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,19 @@
10691069
<xsd:union memberTypes="xsd:boolean xsd:string" />
10701070
</xsd:simpleType>
10711071
</xsd:attribute>
1072+
<xsd:attribute name="explicit-declarations-only" default="false">
1073+
<xsd:annotation>
1074+
<xsd:documentation><![CDATA[
1075+
If automatic declaration is enabled (see 'auto-startup'), if this is set to 'true', only beans
1076+
(queues, exchanges, bindings) that are explicity configured to be declared by this admin (see
1077+
'declared-by') will be declared.
1078+
Default value is 'false' which means all beans with no explicit 'declared-by' will be declared.
1079+
]]></xsd:documentation>
1080+
</xsd:annotation>
1081+
<xsd:simpleType>
1082+
<xsd:union memberTypes="xsd:boolean xsd:string" />
1083+
</xsd:simpleType>
1084+
</xsd:attribute>
10721085
</xsd:complexType>
10731086
</xsd:element>
10741087

spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/AdminParserTests.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
2727
import org.springframework.amqp.rabbit.core.RabbitAdmin;
2828
import org.springframework.amqp.rabbit.core.RabbitTemplate;
29+
import org.springframework.amqp.utils.test.TestUtils;
2930
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
3031
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3132
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
@@ -56,20 +57,28 @@ public final class AdminParserTests {
5657
private boolean initialisedWithTemplate;
5758

5859
@Test
59-
public void testInvalid() throws Exception {
60-
contextIndex = 1;
61-
validContext = false;
62-
doTest();
60+
public void testValid0() throws Exception {
61+
this.expectedAutoStartup = true;
62+
this.contextIndex = 0;
63+
this.validContext = true;
64+
doTest(false);
6365
}
6466

6567
@Test
66-
public void testValid() throws Exception {
67-
contextIndex = 2;
68-
validContext = true;
69-
doTest();
68+
public void testInvalid1() throws Exception {
69+
this.contextIndex = 1;
70+
this.validContext = false;
71+
doTest(false);
7072
}
7173

72-
private void doTest() throws Exception {
74+
@Test
75+
public void testValid2() throws Exception {
76+
this.contextIndex = 2;
77+
this.validContext = true;
78+
doTest(true);
79+
}
80+
81+
private void doTest(boolean explicit) throws Exception {
7382
// Create context
7483
DefaultListableBeanFactory beanFactory = loadContext();
7584
if (beanFactory == null) {
@@ -79,19 +88,20 @@ private void doTest() throws Exception {
7988

8089
// Validate values
8190
RabbitAdmin admin;
82-
if (StringUtils.hasText(adminBeanName)) {
83-
admin = beanFactory.getBean(adminBeanName, RabbitAdmin.class);
91+
if (StringUtils.hasText(this.adminBeanName)) {
92+
admin = beanFactory.getBean(this.adminBeanName, RabbitAdmin.class);
8493
}
8594
else {
8695
admin = beanFactory.getBean(RabbitAdmin.class);
8796
}
88-
assertThat(admin.isAutoStartup()).isEqualTo(expectedAutoStartup);
89-
assertThat(admin.getRabbitTemplate().getConnectionFactory()).isEqualTo(beanFactory.getBean(ConnectionFactory.class));
97+
assertThat(admin.isAutoStartup()).isEqualTo(this.expectedAutoStartup);
98+
assertThat(admin.getRabbitTemplate().getConnectionFactory())
99+
.isEqualTo(beanFactory.getBean(ConnectionFactory.class));
90100

91-
if (initialisedWithTemplate) {
101+
if (this.initialisedWithTemplate) {
92102
assertThat(admin.getRabbitTemplate()).isEqualTo(beanFactory.getBean(RabbitTemplate.class));
93103
}
94-
104+
assertThat(TestUtils.getPropertyValue(admin, "explicitDeclarationsOnly", Boolean.class)).isEqualTo(explicit);
95105
}
96106

97107
/**
@@ -107,12 +117,12 @@ private DefaultListableBeanFactory loadContext() {
107117
beanFactory = new DefaultListableBeanFactory();
108118
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
109119
reader.loadBeanDefinitions(resource);
110-
if (!validContext) {
120+
if (!this.validContext) {
111121
fail("Context " + resource + " failed to load");
112122
}
113123
}
114124
catch (BeanDefinitionParsingException e) {
115-
if (validContext) {
125+
if (this.validContext) {
116126
// Context expected to be valid - throw an exception up
117127
throw e;
118128
}

spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitAdminDeclarationTests.java

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import static org.mockito.ArgumentMatchers.eq;
2626
import static org.mockito.ArgumentMatchers.isNull;
2727
import static org.mockito.BDDMockito.given;
28+
import static org.mockito.BDDMockito.willAnswer;
2829
import static org.mockito.BDDMockito.willReturn;
2930
import static org.mockito.Mockito.doAnswer;
3031
import static org.mockito.Mockito.mock;
@@ -261,16 +262,31 @@ public void testJavaConfig() throws Exception {
261262
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
262263
Config.listener1.onCreate(Config.conn1);
263264
verify(Config.channel1).queueDeclare("foo", true, false, false, new HashMap<>());
265+
verify(Config.channel1, never()).queueDeclare("baz", true, false, false, new HashMap<>());
266+
verify(Config.channel1).queueDeclare("qux", true, false, false, new HashMap<>());
264267
verify(Config.channel1).exchangeDeclare("bar", "direct", true, false, true, new HashMap<String, Object>());
265268
verify(Config.channel1).queueBind("foo", "bar", "foo", null);
266269

267270
Config.listener2.onCreate(Config.conn2);
268271
verify(Config.channel2, never())
269272
.queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), isNull());
273+
verify(Config.channel1, never()).queueDeclare("baz", true, false, false, new HashMap<>());
274+
verify(Config.channel2).queueDeclare("qux", true, false, false, new HashMap<>());
270275
verify(Config.channel2, never())
271276
.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(),
272277
anyBoolean(), anyMap());
273278
verify(Config.channel2, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), anyMap());
279+
280+
Config.listener3.onCreate(Config.conn3);
281+
verify(Config.channel3, never())
282+
.queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), isNull());
283+
verify(Config.channel3).queueDeclare("baz", true, false, false, new HashMap<>());
284+
verify(Config.channel3, never()).queueDeclare("qux", true, false, false, new HashMap<>());
285+
verify(Config.channel3, never())
286+
.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(),
287+
anyBoolean(), anyMap());
288+
verify(Config.channel3, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), anyMap());
289+
274290
context.close();
275291
}
276292

@@ -334,21 +350,28 @@ public static class Config {
334350

335351
private static Connection conn2 = mock(Connection.class);
336352

353+
private static Connection conn3 = mock(Connection.class);
354+
337355
private static Channel channel1 = mock(Channel.class);
338356

339357
private static Channel channel2 = mock(Channel.class);
340358

359+
private static Channel channel3 = mock(Channel.class);
360+
341361
private static ConnectionListener listener1;
342362

343363
private static ConnectionListener listener2;
344364

365+
private static ConnectionListener listener3;
366+
345367
@Bean
346368
public ConnectionFactory cf1() throws IOException {
347369
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
348370
when(connectionFactory.createConnection()).thenReturn(conn1);
349371
when(conn1.createChannel(false)).thenReturn(channel1);
350-
when(channel1.queueDeclare("foo", true, false, false, new HashMap<>()))
351-
.thenReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
372+
willAnswer(inv -> {
373+
return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0);
374+
}).given(channel1).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
352375
doAnswer(invocation -> {
353376
listener1 = invocation.getArgument(0);
354377
return null;
@@ -361,36 +384,69 @@ public ConnectionFactory cf2() throws IOException {
361384
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
362385
when(connectionFactory.createConnection()).thenReturn(conn2);
363386
when(conn2.createChannel(false)).thenReturn(channel2);
364-
when(channel2.queueDeclare("foo", true, false, false, null))
365-
.thenReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
387+
willAnswer(inv -> {
388+
return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0);
389+
}).given(channel2).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
366390
doAnswer(invocation -> {
367391
listener2 = invocation.getArgument(0);
368392
return null;
369393
}).when(connectionFactory).addConnectionListener(any(ConnectionListener.class));
370394
return connectionFactory;
371395
}
372396

397+
@Bean
398+
public ConnectionFactory cf3() throws IOException {
399+
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
400+
when(connectionFactory.createConnection()).thenReturn(conn3);
401+
when(conn3.createChannel(false)).thenReturn(channel3);
402+
willAnswer(inv -> {
403+
return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0);
404+
}).given(channel3).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
405+
doAnswer(invocation -> {
406+
listener3 = invocation.getArgument(0);
407+
return null;
408+
}).when(connectionFactory).addConnectionListener(any(ConnectionListener.class));
409+
return connectionFactory;
410+
}
411+
373412
@Bean
374413
public RabbitAdmin admin1() throws IOException {
375414
RabbitAdmin rabbitAdmin = new RabbitAdmin(cf1());
376-
rabbitAdmin.afterPropertiesSet();
377415
return rabbitAdmin;
378416
}
379417

380418
@Bean
381419
public RabbitAdmin admin2() throws IOException {
382420
RabbitAdmin rabbitAdmin = new RabbitAdmin(cf2());
383-
rabbitAdmin.afterPropertiesSet();
384421
return rabbitAdmin;
385422
}
386423

387424
@Bean
388-
public Queue queue() throws IOException {
425+
public RabbitAdmin admin3() throws IOException {
426+
RabbitAdmin rabbitAdmin = new RabbitAdmin(cf3());
427+
rabbitAdmin.setExplicitDeclarationsOnly(true);
428+
return rabbitAdmin;
429+
}
430+
431+
@Bean
432+
public Queue queueFoo() throws IOException {
389433
Queue queue = new Queue("foo");
390434
queue.setAdminsThatShouldDeclare(admin1());
391435
return queue;
392436
}
393437

438+
@Bean
439+
public Queue queueBaz() throws IOException {
440+
Queue queue = new Queue("baz");
441+
queue.setAdminsThatShouldDeclare(admin3());
442+
return queue;
443+
}
444+
445+
@Bean
446+
public Queue queueQux() {
447+
return new Queue("qux");
448+
}
449+
394450
@Bean
395451
public Exchange exchange() throws IOException {
396452
DirectExchange exchange = new DirectExchange("bar");

spring-rabbit/src/test/resources/org/springframework/amqp/rabbit/config/AdminParserTests-2-context.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<rabbit:connection-factory id="connectionFactory" host="localhost" />
99

1010
<!-- Valid configuration -->
11-
<rabbit:admin id="admin-test" connection-factory="connectionFactory" auto-startup="false"/>
11+
<rabbit:admin id="admin-test" connection-factory="connectionFactory" auto-startup="false"
12+
explicit-declarations-only="true"/>
1213

1314
</beans>

src/reference/asciidoc/amqp.adoc

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4611,6 +4611,8 @@ public SimpleMessageListenerContainer container(ConnectionFactory connectionFact
46114611

46124612
By default, all queues, exchanges, and bindings are declared by all `RabbitAdmin` instances (assuming they have `auto-startup="true"`) in the application context.
46134613

4614+
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.
4615+
46144616
NOTE: Starting with the 1.2 release, you can conditionally declare these elements.
46154617
This is particularly useful when an application connects to multiple brokers and needs to specify with which brokers a particular element should be declared.
46164618

@@ -4626,13 +4628,15 @@ The properties are available as attributes in the namespace, as shown in the fol
46264628
46274629
<rabbit:admin id="admin2" connection-factory="CF2" />
46284630
4629-
<rabbit:queue id="declaredByBothAdminsImplicitly" />
4631+
<rabbit:admin id="admin3" connection-factory="CF3" explicit-declarations-only="true" />
4632+
4633+
<rabbit:queue id="declaredByAdmin1AndAdmin2Implicitly" />
46304634
4631-
<rabbit:queue id="declaredByBothAdmins" declared-by="admin1, admin2" />
4635+
<rabbit:queue id="declaredByAdmin1AndAdmin2" declared-by="admin1, admin2" />
46324636
46334637
<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />
46344638
4635-
<rabbit:queue id="notDeclaredByAny" auto-declare="false" />
4639+
<rabbit:queue id="notDeclaredByAllExceptAdmin3" auto-declare="false" />
46364640
46374641
<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
46384642
<rabbit:bindings>
@@ -4642,7 +4646,7 @@ The properties are available as attributes in the namespace, as shown in the fol
46424646
----
46434647
====
46444648

4645-
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).
4649+
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).
46464650

46474651
Similarly, you can use Java-based `@Configuration` to achieve the same effect.
46484652
In the following example, the components are declared by `admin1` but not by`admin2`:

src/reference/asciidoc/whats-new.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ See <<message-listener-adapter>> for more information.
7575
The `ExchangeBuilder` and `QueueBuilder` fluent APIs used to create `Exchange` and `Queue` objects for declaration by `RabbitAdmin` now support "well known" arguments.
7676
See <<builder-api>> for more information.
7777

78+
The `RabbitAdmin` has a new property `explicitDeclarationsOnly`.
79+
See <<conditional-declaration>> for more information.
80+
7881
===== Connection Factory Changes
7982

8083
The `CachingConnectionFactory` has a new property `shuffleAddresses`.

0 commit comments

Comments
 (0)