Skip to content

Commit 3ebe831

Browse files
garyrussellartembilan
authored andcommitted
GH-1074: Admin explicitDeclarationsOnly Property
Resolves #1074 Cherry-pick to 2.1.x # Conflicts: # spring-rabbit/src/test/java/org/springframework/amqp/rabbit/config/AdminParserTests.java # src/reference/asciidoc/whats-new.adoc
1 parent 37a466a commit 3ebe831

File tree

8 files changed

+135
-30
lines changed

8 files changed

+135
-30
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;
@@ -432,6 +434,17 @@ public Properties getQueueProperties(final String queueName) {
432434
});
433435
}
434436

437+
/**
438+
* Set to true to only declare {@link Declarable} beans that are explicitly configured
439+
* to be declared by this admin.
440+
* @param explicitDeclarationsOnly true to ignore beans with no admin declaration
441+
* configuration.
442+
* @since 2.1.9
443+
*/
444+
public void setExplicitDeclarationsOnly(boolean explicitDeclarationsOnly) {
445+
this.explicitDeclarationsOnly = explicitDeclarationsOnly;
446+
}
447+
435448
/**
436449
* Set a retry template for auto declarations. There is a race condition with
437450
* auto-delete, exclusive queues in that the queue might still exist for a short time,
@@ -657,12 +670,16 @@ else if (declarable instanceof Binding) {
657670
*/
658671
private <T extends Declarable> Collection<T> filterDeclarables(Collection<T> declarables) {
659672
return declarables.stream()
660-
.filter(d -> d.shouldDeclare() // NOSONAR boolean complexity
661-
&& (d.getDeclaringAdmins().isEmpty() || d.getDeclaringAdmins().contains(this)
662-
|| (this.beanName != null && d.getDeclaringAdmins().contains(this.beanName))))
673+
.filter(dec -> dec.shouldDeclare() && declarableByMe(dec))
663674
.collect(Collectors.toList());
664675
}
665676

677+
private <T extends Declarable> boolean declarableByMe(T dec) {
678+
return (dec.getDeclaringAdmins().isEmpty() && !this.explicitDeclarationsOnly) // NOSONAR boolean complexity
679+
|| dec.getDeclaringAdmins().contains(this)
680+
|| (this.beanName != null && dec.getDeclaringAdmins().contains(this.beanName));
681+
}
682+
666683
// private methods for declaring Exchanges, Queues, and Bindings on a Channel
667684

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

spring-rabbit/src/main/resources/org/springframework/amqp/rabbit/config/spring-rabbit-2.1.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: 24 additions & 15 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,19 @@ 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
}
8897
assertEquals(expectedAutoStartup, admin.isAutoStartup());
8998
assertEquals(beanFactory.getBean(ConnectionFactory.class), admin.getRabbitTemplate().getConnectionFactory());
9099

91-
if (initialisedWithTemplate) {
100+
if (this.initialisedWithTemplate) {
92101
assertEquals(beanFactory.getBean(RabbitTemplate.class), admin.getRabbitTemplate());
93102
}
94-
103+
assertEquals(explicit, TestUtils.getPropertyValue(admin, "explicitDeclarationsOnly", Boolean.class));
95104
}
96105

97106
/**
@@ -107,12 +116,12 @@ private DefaultListableBeanFactory loadContext() {
107116
beanFactory = new DefaultListableBeanFactory();
108117
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
109118
reader.loadBeanDefinitions(resource);
110-
if (!validContext) {
119+
if (!this.validContext) {
111120
fail("Context " + resource + " failed to load");
112121
}
113122
}
114123
catch (BeanDefinitionParsingException e) {
115-
if (validContext) {
124+
if (this.validContext) {
116125
// Context expected to be valid - throw an exception up
117126
throw e;
118127
}

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
@@ -28,6 +28,7 @@
2828
import static org.mockito.ArgumentMatchers.eq;
2929
import static org.mockito.ArgumentMatchers.isNull;
3030
import static org.mockito.BDDMockito.given;
31+
import static org.mockito.BDDMockito.willAnswer;
3132
import static org.mockito.BDDMockito.willReturn;
3233
import static org.mockito.Mockito.doAnswer;
3334
import static org.mockito.Mockito.mock;
@@ -273,16 +274,31 @@ public void testJavaConfig() throws Exception {
273274
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
274275
Config.listener1.onCreate(Config.conn1);
275276
verify(Config.channel1).queueDeclare("foo", true, false, false, new HashMap<>());
277+
verify(Config.channel1, never()).queueDeclare("baz", true, false, false, new HashMap<>());
278+
verify(Config.channel1).queueDeclare("qux", true, false, false, new HashMap<>());
276279
verify(Config.channel1).exchangeDeclare("bar", "direct", true, false, true, new HashMap<String, Object>());
277280
verify(Config.channel1).queueBind("foo", "bar", "foo", null);
278281

279282
Config.listener2.onCreate(Config.conn2);
280283
verify(Config.channel2, never())
281284
.queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), isNull());
285+
verify(Config.channel1, never()).queueDeclare("baz", true, false, false, new HashMap<>());
286+
verify(Config.channel2).queueDeclare("qux", true, false, false, new HashMap<>());
282287
verify(Config.channel2, never())
283288
.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(),
284289
anyBoolean(), anyMap());
285290
verify(Config.channel2, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), anyMap());
291+
292+
Config.listener3.onCreate(Config.conn3);
293+
verify(Config.channel3, never())
294+
.queueDeclare(eq("foo"), anyBoolean(), anyBoolean(), anyBoolean(), isNull());
295+
verify(Config.channel3).queueDeclare("baz", true, false, false, new HashMap<>());
296+
verify(Config.channel3, never()).queueDeclare("qux", true, false, false, new HashMap<>());
297+
verify(Config.channel3, never())
298+
.exchangeDeclare(eq("bar"), eq("direct"), anyBoolean(), anyBoolean(),
299+
anyBoolean(), anyMap());
300+
verify(Config.channel3, never()).queueBind(eq("foo"), eq("bar"), eq("foo"), anyMap());
301+
286302
context.close();
287303
}
288304

@@ -346,21 +362,28 @@ public static class Config {
346362

347363
private static Connection conn2 = mock(Connection.class);
348364

365+
private static Connection conn3 = mock(Connection.class);
366+
349367
private static Channel channel1 = mock(Channel.class);
350368

351369
private static Channel channel2 = mock(Channel.class);
352370

371+
private static Channel channel3 = mock(Channel.class);
372+
353373
private static ConnectionListener listener1;
354374

355375
private static ConnectionListener listener2;
356376

377+
private static ConnectionListener listener3;
378+
357379
@Bean
358380
public ConnectionFactory cf1() throws IOException {
359381
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
360382
when(connectionFactory.createConnection()).thenReturn(conn1);
361383
when(conn1.createChannel(false)).thenReturn(channel1);
362-
when(channel1.queueDeclare("foo", true, false, false, new HashMap<>()))
363-
.thenReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
384+
willAnswer(inv -> {
385+
return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0);
386+
}).given(channel1).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
364387
doAnswer(invocation -> {
365388
listener1 = invocation.getArgument(0);
366389
return null;
@@ -373,36 +396,69 @@ public ConnectionFactory cf2() throws IOException {
373396
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
374397
when(connectionFactory.createConnection()).thenReturn(conn2);
375398
when(conn2.createChannel(false)).thenReturn(channel2);
376-
when(channel2.queueDeclare("foo", true, false, false, null))
377-
.thenReturn(new AMQImpl.Queue.DeclareOk("foo", 0, 0));
399+
willAnswer(inv -> {
400+
return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0);
401+
}).given(channel2).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
378402
doAnswer(invocation -> {
379403
listener2 = invocation.getArgument(0);
380404
return null;
381405
}).when(connectionFactory).addConnectionListener(any(ConnectionListener.class));
382406
return connectionFactory;
383407
}
384408

409+
@Bean
410+
public ConnectionFactory cf3() throws IOException {
411+
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
412+
when(connectionFactory.createConnection()).thenReturn(conn3);
413+
when(conn3.createChannel(false)).thenReturn(channel3);
414+
willAnswer(inv -> {
415+
return new AMQImpl.Queue.DeclareOk(inv.getArgument(0), 0, 0);
416+
}).given(channel3).queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), any());
417+
doAnswer(invocation -> {
418+
listener3 = invocation.getArgument(0);
419+
return null;
420+
}).when(connectionFactory).addConnectionListener(any(ConnectionListener.class));
421+
return connectionFactory;
422+
}
423+
385424
@Bean
386425
public RabbitAdmin admin1() throws IOException {
387426
RabbitAdmin rabbitAdmin = new RabbitAdmin(cf1());
388-
rabbitAdmin.afterPropertiesSet();
389427
return rabbitAdmin;
390428
}
391429

392430
@Bean
393431
public RabbitAdmin admin2() throws IOException {
394432
RabbitAdmin rabbitAdmin = new RabbitAdmin(cf2());
395-
rabbitAdmin.afterPropertiesSet();
396433
return rabbitAdmin;
397434
}
398435

399436
@Bean
400-
public Queue queue() throws IOException {
437+
public RabbitAdmin admin3() throws IOException {
438+
RabbitAdmin rabbitAdmin = new RabbitAdmin(cf3());
439+
rabbitAdmin.setExplicitDeclarationsOnly(true);
440+
return rabbitAdmin;
441+
}
442+
443+
@Bean
444+
public Queue queueFoo() throws IOException {
401445
Queue queue = new Queue("foo");
402446
queue.setAdminsThatShouldDeclare(admin1());
403447
return queue;
404448
}
405449

450+
@Bean
451+
public Queue queueBaz() throws IOException {
452+
Queue queue = new Queue("baz");
453+
queue.setAdminsThatShouldDeclare(admin3());
454+
return queue;
455+
}
456+
457+
@Bean
458+
public Queue queueQux() {
459+
return new Queue("qux");
460+
}
461+
406462
@Bean
407463
public Exchange exchange() throws IOException {
408464
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
@@ -4356,6 +4356,8 @@ You can revert to the previous behavior by setting the `RabbitAdmin` property ca
43564356

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

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

@@ -4371,13 +4373,15 @@ The properties are available as attributes in the namespace, as shown in the fol
43714373
43724374
<rabbit:admin id="admin2" connection-factory="CF2" />
43734375
4374-
<rabbit:queue id="declaredByBothAdminsImplicitly" />
4376+
<rabbit:admin id="admin3" connection-factory="CF3" explicit-declarations-only="true" />
4377+
4378+
<rabbit:queue id="declaredByAdmin1AndAdmin2Implicitly" />
43754379
4376-
<rabbit:queue id="declaredByBothAdmins" declared-by="admin1, admin2" />
4380+
<rabbit:queue id="declaredByAdmin1AndAdmin2" declared-by="admin1, admin2" />
43774381
43784382
<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />
43794383
4380-
<rabbit:queue id="notDeclaredByAny" auto-declare="false" />
4384+
<rabbit:queue id="notDeclaredByAllExceptAdmin3" auto-declare="false" />
43814385
43824386
<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
43834387
<rabbit:bindings>
@@ -4387,7 +4391,7 @@ The properties are available as attributes in the namespace, as shown in the fol
43874391
----
43884392
====
43894393

4390-
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).
4394+
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).
43914395

43924396
Similarly, you can use Java-based `@Configuration` to achieve the same effect.
43934397
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
@@ -124,6 +124,9 @@ The appenders now support the `SaslConfig` property.
124124

125125
See <<logging>> for more information.
126126

127+
The `RabbitAdmin` has a new property `explicitDeclarationsOnly`.
128+
See <<conditional-declaration>> for more information.
129+
127130
===== Connection Factory Changes
128131

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

0 commit comments

Comments
 (0)