Add ConnectionDetail support to Rabbit auto-configuration
Update Rabbit auto-configuration so that `RabbitConnectionDetails` beans may be optionally used to provide connection details. See gh-34657 Co-Authored-By: Mortitz Halbritter <mkammerer@vmware.com> Co-Authored-By: Phillip Webb <pwebb@vmware.com>
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.autoconfigure.amqp;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.AbstractConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
@@ -27,6 +29,9 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @param <T> the connection factory type.
|
||||
* @author Chris Bono
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public abstract class AbstractConnectionFactoryConfigurer<T extends AbstractConnectionFactory> {
|
||||
@@ -35,9 +40,31 @@ public abstract class AbstractConnectionFactoryConfigurer<T extends AbstractConn
|
||||
|
||||
private ConnectionNameStrategy connectionNameStrategy;
|
||||
|
||||
private final RabbitConnectionDetails connectionDetails;
|
||||
|
||||
/**
|
||||
* Creates a new configurer that will configure the connection factory using the given
|
||||
* {@code properties}.
|
||||
* @param properties the properties to use to configure the connection factory
|
||||
*/
|
||||
protected AbstractConnectionFactoryConfigurer(RabbitProperties properties) {
|
||||
Assert.notNull(properties, "RabbitProperties must not be null");
|
||||
this(properties, new PropertiesRabbitConnectionDetails(properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new configurer that will configure the connection factory using the given
|
||||
* {@code properties} and {@code connectionDetails}, with the latter taking priority.
|
||||
* @param properties the properties to use to configure the connection factory
|
||||
* @param connectionDetails the connection details to use to configure the connection
|
||||
* factory
|
||||
* @since 3.1.0
|
||||
*/
|
||||
protected AbstractConnectionFactoryConfigurer(RabbitProperties properties,
|
||||
RabbitConnectionDetails connectionDetails) {
|
||||
Assert.notNull(properties, "Properties must not be null");
|
||||
Assert.notNull(connectionDetails, "ConnectionDetails must not be null");
|
||||
this.rabbitProperties = properties;
|
||||
this.connectionDetails = connectionDetails;
|
||||
}
|
||||
|
||||
protected final ConnectionNameStrategy getConnectionNameStrategy() {
|
||||
@@ -55,7 +82,11 @@ public abstract class AbstractConnectionFactoryConfigurer<T extends AbstractConn
|
||||
public final void configure(T connectionFactory) {
|
||||
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(this.rabbitProperties::determineAddresses).to(connectionFactory::setAddresses);
|
||||
String addresses = this.connectionDetails.getAddresses()
|
||||
.stream()
|
||||
.map((address) -> address.host() + ":" + address.port())
|
||||
.collect(Collectors.joining(","));
|
||||
map.from(addresses).to(connectionFactory::setAddresses);
|
||||
map.from(this.rabbitProperties::getAddressShuffleMode)
|
||||
.whenNonNull()
|
||||
.to(connectionFactory::setAddressShuffleMode);
|
||||
|
||||
@@ -25,12 +25,32 @@ import org.springframework.boot.context.properties.PropertyMapper;
|
||||
* Configures Rabbit {@link CachingConnectionFactory} with sensible defaults.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class CachingConnectionFactoryConfigurer extends AbstractConnectionFactoryConfigurer<CachingConnectionFactory> {
|
||||
|
||||
/**
|
||||
* Creates a new configurer that will configure the connection factory using the given
|
||||
* {@code properties}.
|
||||
* @param properties the properties to use to configure the connection factory
|
||||
*/
|
||||
public CachingConnectionFactoryConfigurer(RabbitProperties properties) {
|
||||
super(properties);
|
||||
this(properties, new PropertiesRabbitConnectionDetails(properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new configurer that will configure the connection factory using the given
|
||||
* {@code properties} and {@code connectionDetails}, with the latter taking priority.
|
||||
* @param properties the properties to use to configure the connection factory
|
||||
* @param connectionDetails the connection details to use to configure the connection
|
||||
* factory
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public CachingConnectionFactoryConfigurer(RabbitProperties properties, RabbitConnectionDetails connectionDetails) {
|
||||
super(properties, connectionDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.boot.autoconfigure.amqp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Adapts {@link RabbitProperties} to {@link RabbitConnectionDetails}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class PropertiesRabbitConnectionDetails implements RabbitConnectionDetails {
|
||||
|
||||
private final RabbitProperties properties;
|
||||
|
||||
public PropertiesRabbitConnectionDetails(RabbitProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return this.properties.determineUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return this.properties.determinePassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVirtualHost() {
|
||||
return this.properties.determineVirtualHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Address> getAddresses() {
|
||||
List<Address> addresses = new ArrayList<>();
|
||||
for (String address : this.properties.determineAddresses().split(",")) {
|
||||
String[] components = address.split(":");
|
||||
addresses.add(new Address(components[0], Integer.parseInt(components[1])));
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,21 +59,6 @@ import org.springframework.core.io.ResourceLoader;
|
||||
* <li>{@link org.springframework.amqp.core.AmqpAdmin } instance as long as
|
||||
* {@literal spring.rabbitmq.dynamic=true}.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The {@link org.springframework.amqp.rabbit.connection.CachingConnectionFactory} honors
|
||||
* the following properties:
|
||||
* <ul>
|
||||
* <li>{@literal spring.rabbitmq.port} is used to specify the port to which the client
|
||||
* should connect, and defaults to 5672.</li>
|
||||
* <li>{@literal spring.rabbitmq.username} is used to specify the (optional) username.
|
||||
* </li>
|
||||
* <li>{@literal spring.rabbitmq.password} is used to specify the (optional) password.
|
||||
* </li>
|
||||
* <li>{@literal spring.rabbitmq.host} is used to specify the host, and defaults to
|
||||
* {@literal localhost}.</li>
|
||||
* <li>{@literal spring.rabbitmq.virtualHost} is used to specify the (optional) virtual
|
||||
* host to which the client should connect.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Josh Long
|
||||
@@ -82,6 +67,8 @@ import org.springframework.core.io.ResourceLoader;
|
||||
* @author Phillip Webb
|
||||
* @author Artsiom Yudovin
|
||||
* @author Chris Bono
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@@ -93,13 +80,24 @@ public class RabbitAutoConfiguration {
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
protected static class RabbitConnectionFactoryCreator {
|
||||
|
||||
private final RabbitProperties properties;
|
||||
|
||||
private final RabbitConnectionDetails connectionDetails;
|
||||
|
||||
protected RabbitConnectionFactoryCreator(RabbitProperties properties,
|
||||
ObjectProvider<RabbitConnectionDetails> connectionDetails) {
|
||||
this.properties = properties;
|
||||
this.connectionDetails = connectionDetails
|
||||
.getIfAvailable(() -> new PropertiesRabbitConnectionDetails(properties));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(RabbitProperties properties,
|
||||
ResourceLoader resourceLoader, ObjectProvider<CredentialsProvider> credentialsProvider,
|
||||
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader,
|
||||
ObjectProvider<CredentialsProvider> credentialsProvider,
|
||||
ObjectProvider<CredentialsRefreshService> credentialsRefreshService) {
|
||||
RabbitConnectionFactoryBeanConfigurer configurer = new RabbitConnectionFactoryBeanConfigurer(resourceLoader,
|
||||
properties);
|
||||
this.properties, this.connectionDetails);
|
||||
configurer.setCredentialsProvider(credentialsProvider.getIfUnique());
|
||||
configurer.setCredentialsRefreshService(credentialsRefreshService.getIfUnique());
|
||||
return configurer;
|
||||
@@ -107,9 +105,10 @@ public class RabbitAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer(RabbitProperties rabbitProperties,
|
||||
CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer(
|
||||
ObjectProvider<ConnectionNameStrategy> connectionNameStrategy) {
|
||||
CachingConnectionFactoryConfigurer configurer = new CachingConnectionFactoryConfigurer(rabbitProperties);
|
||||
CachingConnectionFactoryConfigurer configurer = new CachingConnectionFactoryConfigurer(this.properties,
|
||||
this.connectionDetails);
|
||||
configurer.setConnectionNameStrategy(connectionNameStrategy.getIfUnique());
|
||||
return configurer;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.boot.autoconfigure.amqp;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Details required to establish a connection to a RabbitMQ service.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface RabbitConnectionDetails extends ConnectionDetails {
|
||||
|
||||
/**
|
||||
* Login user to authenticate to the broker.
|
||||
* @return the login user to authenticate to the broker or {@code null}
|
||||
*/
|
||||
default String getUsername() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to authenticate against the broker.
|
||||
* @return the login to authenticate against the broker or {@code null}
|
||||
*/
|
||||
default String getPassword() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual host to use when connecting to the broker.
|
||||
* @return the virtual host to use when connecting to the broker or {@code null}
|
||||
*/
|
||||
default String getVirtualHost() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of addresses to which the client should connect. Must return at least one
|
||||
* address.
|
||||
* @return the list of addresses to which the client should connect
|
||||
*/
|
||||
List<Address> getAddresses();
|
||||
|
||||
/**
|
||||
* Returns the first address.
|
||||
* @return the first address
|
||||
* @throws IllegalStateException if the address list is empty
|
||||
*/
|
||||
default Address getFirstAddress() {
|
||||
List<Address> addresses = getAddresses();
|
||||
Assert.state(!addresses.isEmpty(), "Address list is empty");
|
||||
return addresses.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* A RabbitMQ address.
|
||||
*
|
||||
* @param host the host
|
||||
* @param port the port
|
||||
*/
|
||||
record Address(String host, int port) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.rabbitmq.client.impl.CredentialsProvider;
|
||||
import com.rabbitmq.client.impl.CredentialsRefreshService;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails.Address;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -30,6 +31,9 @@ import org.springframework.util.Assert;
|
||||
* Configures {@link RabbitConnectionFactoryBean} with sensible defaults.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class RabbitConnectionFactoryBeanConfigurer {
|
||||
@@ -38,13 +42,39 @@ public class RabbitConnectionFactoryBeanConfigurer {
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
private final RabbitConnectionDetails connectionDetails;
|
||||
|
||||
private CredentialsProvider credentialsProvider;
|
||||
|
||||
private CredentialsRefreshService credentialsRefreshService;
|
||||
|
||||
/**
|
||||
* Creates a new configurer that will use the given {@code resourceLoader} and
|
||||
* {@code properties}.
|
||||
* @param resourceLoader the resource loader
|
||||
* @param properties the properties
|
||||
*/
|
||||
public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties) {
|
||||
this(resourceLoader, properties, new PropertiesRabbitConnectionDetails(properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new configurer that will use the given {@code resourceLoader},
|
||||
* {@code properties}, and {@code connectionDetails}. The connection details have
|
||||
* priority over the properties.
|
||||
* @param resourceLoader the resource loader
|
||||
* @param properties the properties
|
||||
* @param connectionDetails the connection details.
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public RabbitConnectionFactoryBeanConfigurer(ResourceLoader resourceLoader, RabbitProperties properties,
|
||||
RabbitConnectionDetails connectionDetails) {
|
||||
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
|
||||
Assert.notNull(properties, "Properties must not be null");
|
||||
Assert.notNull(connectionDetails, "ConnectionDetails must not be null");
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.rabbitProperties = properties;
|
||||
this.connectionDetails = connectionDetails;
|
||||
}
|
||||
|
||||
public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
|
||||
@@ -65,12 +95,13 @@ public class RabbitConnectionFactoryBeanConfigurer {
|
||||
public void configure(RabbitConnectionFactoryBean factory) {
|
||||
Assert.notNull(factory, "RabbitConnectionFactoryBean must not be null");
|
||||
factory.setResourceLoader(this.resourceLoader);
|
||||
Address address = this.connectionDetails.getFirstAddress();
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(this.rabbitProperties::determineHost).whenNonNull().to(factory::setHost);
|
||||
map.from(this.rabbitProperties::determinePort).to(factory::setPort);
|
||||
map.from(this.rabbitProperties::determineUsername).whenNonNull().to(factory::setUsername);
|
||||
map.from(this.rabbitProperties::determinePassword).whenNonNull().to(factory::setPassword);
|
||||
map.from(this.rabbitProperties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost);
|
||||
map.from(address::host).whenNonNull().to(factory::setHost);
|
||||
map.from(address::port).to(factory::setPort);
|
||||
map.from(this.connectionDetails::getUsername).whenNonNull().to(factory::setUsername);
|
||||
map.from(this.connectionDetails::getPassword).whenNonNull().to(factory::setPassword);
|
||||
map.from(this.connectionDetails::getVirtualHost).whenNonNull().to(factory::setVirtualHost);
|
||||
map.from(this.rabbitProperties::getRequestedHeartbeat)
|
||||
.whenNonNull()
|
||||
.asInt(Duration::getSeconds)
|
||||
|
||||
@@ -96,6 +96,9 @@ import static org.mockito.Mockito.mock;
|
||||
* @author Gary Russell
|
||||
* @author HaiTao Zhang
|
||||
* @author Franjo Zilic
|
||||
* @author Moritz Halbritter
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class RabbitAutoConfigurationTests {
|
||||
@@ -169,6 +172,26 @@ class RabbitAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testConnectionFactoryWithOverridesWhenUsingConnectionDetails() {
|
||||
this.contextRunner.withUserConfiguration(TestConfiguration.class, ConnectionDetailsConfiguration.class)
|
||||
.withPropertyValues("spring.rabbitmq.host:remote-server", "spring.rabbitmq.port:9000",
|
||||
"spring.rabbitmq.username:alice", "spring.rabbitmq.password:secret",
|
||||
"spring.rabbitmq.virtual_host:/vhost")
|
||||
.run((context) -> {
|
||||
CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class);
|
||||
assertThat(connectionFactory.getHost()).isEqualTo("rabbit.example.com");
|
||||
assertThat(connectionFactory.getPort()).isEqualTo(12345);
|
||||
assertThat(connectionFactory.getVirtualHost()).isEqualTo("/vhost-1");
|
||||
assertThat(connectionFactory.getUsername()).isEqualTo("user-1");
|
||||
assertThat(connectionFactory.getRabbitConnectionFactory().getPassword()).isEqualTo("password-1");
|
||||
List<Address> addresses = (List<Address>) ReflectionTestUtils.getField(connectionFactory, "addresses");
|
||||
assertThat(addresses).containsExactly(new Address("rabbit.example.com", 12345),
|
||||
new Address("rabbit2.example.com", 23456));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testConnectionFactoryWithCustomConnectionNameStrategy() {
|
||||
@@ -1218,6 +1241,38 @@ class RabbitAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ConnectionDetailsConfiguration {
|
||||
|
||||
@Bean
|
||||
RabbitConnectionDetails rabbitConnectionDetails() {
|
||||
return new RabbitConnectionDetails() {
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return "user-1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return "password-1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVirtualHost() {
|
||||
return "/vhost-1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Address> getAddresses() {
|
||||
return List.of(new Address("rabbit.example.com", 12345), new Address("rabbit2.example.com", 23456));
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestListener {
|
||||
|
||||
@RabbitListener(queues = "test", autoStartup = "false")
|
||||
|
||||
Reference in New Issue
Block a user