GH-1074: Admin explicitDeclarationsOnly Property
Resolves https://github.com/spring-projects/spring-amqp/issues/1074 Cherry-pick to 2.1.x
This commit is contained in:
committed by
Artem Bilan
parent
4b19b61d1e
commit
accd202a1a
@@ -73,5 +73,7 @@ class AdminParser extends AbstractSingleBeanDefinitionParser {
|
||||
}
|
||||
|
||||
NamespaceUtils.setValueIfAttributeDefined(builder, element, IGNORE_DECLARATION_EXCEPTIONS);
|
||||
NamespaceUtils.setValueIfAttributeDefined(builder, element, "explicit-declarations-only");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 <T extends Declarable> Collection<T> filterDeclarables(Collection<T> 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 <T extends Declarable> 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 {
|
||||
|
||||
@@ -1069,6 +1069,19 @@
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string" />
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="explicit-declarations-only" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
If automatic declaration is enabled (see 'auto-startup'), if this is set to 'true', only beans
|
||||
(queues, exchanges, bindings) that are explicity configured to be declared by this admin (see
|
||||
'declared-by') will be declared.
|
||||
Default value is 'false' which means all beans with no explicit 'declared-by' will be declared.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string" />
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<String, Object>());
|
||||
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");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<rabbit:connection-factory id="connectionFactory" host="localhost" />
|
||||
|
||||
<!-- Valid configuration -->
|
||||
<rabbit:admin id="admin-test" connection-factory="connectionFactory" auto-startup="false"/>
|
||||
<rabbit:admin id="admin-test" connection-factory="connectionFactory" auto-startup="false"
|
||||
explicit-declarations-only="true"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -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
|
||||
|
||||
<rabbit:admin id="admin2" connection-factory="CF2" />
|
||||
|
||||
<rabbit:queue id="declaredByBothAdminsImplicitly" />
|
||||
<rabbit:admin id="admin3" connection-factory="CF3" explicit-declarations-only="true" />
|
||||
|
||||
<rabbit:queue id="declaredByBothAdmins" declared-by="admin1, admin2" />
|
||||
<rabbit:queue id="declaredByAdmin1AndAdmin2Implicitly" />
|
||||
|
||||
<rabbit:queue id="declaredByAdmin1AndAdmin2" declared-by="admin1, admin2" />
|
||||
|
||||
<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />
|
||||
|
||||
<rabbit:queue id="notDeclaredByAny" auto-declare="false" />
|
||||
<rabbit:queue id="notDeclaredByAllExceptAdmin3" auto-declare="false" />
|
||||
|
||||
<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
|
||||
<rabbit:bindings>
|
||||
@@ -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`:
|
||||
|
||||
@@ -75,6 +75,9 @@ See <<message-listener-adapter>> 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 <<builder-api>> for more information.
|
||||
|
||||
The `RabbitAdmin` has a new property `explicitDeclarationsOnly`.
|
||||
See <<conditional-declaration>> for more information.
|
||||
|
||||
===== Connection Factory Changes
|
||||
|
||||
The `CachingConnectionFactory` has a new property `shuffleAddresses`.
|
||||
|
||||
Reference in New Issue
Block a user