Spring通过代码Bean配置Azure Service Bus JMS多连接串报错问题
纯代码Bean配置Azure Service Bus JMS(无配置文件依赖、支持多连接串)
问题背景
当前使用如下Maven依赖实现Azure Service Bus主题的消息收发功能:
<dependency> <groupId>com.azure.spring</groupId> <artifactId>spring-cloud-azure-starter-servicebus-jms</artifactId> <version>4.2.0</version> </dependency>
因业务需要配置多个连接字符串,要求通过Spring Bean代码形式完成配置,不依赖application.properties配置文件。最初编写的自定义AzureServiceBusJmsProperties配置Bean如下:
@Bean @Primary public AzureServiceBusJmsProperties priceListJmsProperties() { var properties = new AzureServiceBusJmsProperties(); properties.setConnectionString(connectionString); properties.setPricingTier("standard"); properties.setTopicClientId(priceListTopicName); return properties; }
调试时发现该配置对象会被实例化两次:第一次加载了手动设置的正确配置值,第二次生成的实例所有配置字段均为null。由于AzureServiceBusJmsProperties内置参数校验逻辑要求必须指定对应配置项,抛出如下错误:
spring.jms.servicebus.connection-string' should be provided
尝试直接自定义ConnectionFactory实现配置,仍然出现相同报错。
报错根因
- 4.2.0版本的starter内置
ServiceBusJmsAutoConfiguration自动配置类,会默认绑定spring.jms.servicebus前缀的配置项,自动生成一个默认的AzureServiceBusJmsProperties实例。手动注册的@PrimaryBean不会替换自动配置流程中生成的这个默认实例,无配置值的默认实例触发参数校验,就会抛出连接串缺失的错误。 - 即使直接手动注册
ConnectionFactory,自动配置类的校验逻辑会在自定义Bean加载前执行,依旧会触发相同报错。
实现方案
完全接管JMS相关Bean的初始化,跳过starter自带的自动配置流程即可,步骤如下:
- 排除starter自带的Service Bus JMS自动配置,避免默认空配置触发校验
// Spring Boot启动类上添加排除配置 @SpringBootApplication(exclude = { com.azure.spring.autoconfigure.jms.ServiceBusJmsAutoConfiguration.class, com.azure.spring.autoconfigure.jms.ServiceBusJmsPoolAutoConfiguration.class }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
- 编写配置类,手动注册多套配置属性、连接工厂、JmsTemplate、监听器容器工厂。以下是双连接串配置示例,不需要依赖
spring.jms.servicebus前缀的配置项:
import com.azure.spring.autoconfigure.jms.AzureServiceBusJmsProperties; import org.apache.qpid.jms.JmsConnectionFactory; import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; import org.springframework.jms.connection.CachingConnectionFactory; import org.springframework.jms.core.JmsTemplate; import jakarta.jms.ConnectionFactory; @Configuration public class MultiServiceBusJmsConfig { // 若完全不需要读取配置文件,可直接将连接串、ClientID等值硬编码为常量 private static final String PRICE_LIST_CONN_STR = "你的第一套Service Bus连接串"; private static final String PRICE_LIST_CLIENT_ID = "price-list-topic-client"; private static final String ORDER_CONN_STR = "你的第二套Service Bus连接串"; private static final String ORDER_CLIENT_ID = "order-topic-client"; // 构建第一套配置属性 @Bean public AzureServiceBusJmsProperties priceListJmsProperties() { AzureServiceBusJmsProperties properties = new AzureServiceBusJmsProperties(); properties.setConnectionString(PRICE_LIST_CONN_STR); properties.setPricingTier("standard"); properties.setTopicClientId(PRICE_LIST_CLIENT_ID); return properties; } // 构建第二套配置属性 @Bean public AzureServiceBusJmsProperties orderJmsProperties() { AzureServiceBusJmsProperties properties = new AzureServiceBusJmsProperties(); properties.setConnectionString(ORDER_CONN_STR); properties.setPricingTier("standard"); properties.setTopicClientId(ORDER_CLIENT_ID); return properties; } // 通用连接工厂构建方法 private ConnectionFactory buildConnectionFactory(AzureServiceBusJmsProperties props) { String amqpUri = String.format("amqps://%s?amqp.idleTimeout=%d", props.getNamespace(), props.getIdleTimeout()); JmsConnectionFactory factory = new JmsConnectionFactory( props.getUsername(), props.getPassword(), amqpUri ); factory.setClientID(props.getTopicClientId()); // 按需配置会话缓存,提升性能 CachingConnectionFactory cachingFactory = new CachingConnectionFactory(factory); cachingFactory.setSessionCacheSize(10); return cachingFactory; } @Bean @Primary public ConnectionFactory priceListConnectionFactory() { return buildConnectionFactory(priceListJmsProperties()); } @Bean public ConnectionFactory orderConnectionFactory() { return buildConnectionFactory(orderJmsProperties()); } @Bean @Primary public JmsTemplate priceListJmsTemplate(ConnectionFactory priceListConnectionFactory) { return new JmsTemplate(priceListConnectionFactory); } @Bean public JmsTemplate orderJmsTemplate(ConnectionFactory orderConnectionFactory) { return new JmsTemplate(orderConnectionFactory); } @Bean @Primary public DefaultJmsListenerContainerFactory priceListJmsListenerFactory( ConnectionFactory priceListConnectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer ) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); configurer.configure(factory, priceListConnectionFactory); // 主题模式需开启pubSubDomain,队列模式可省略 factory.setPubSubDomain(true); factory.setSessionTransacted(true); return factory; } @Bean public DefaultJmsListenerContainerFactory orderJmsListenerFactory( ConnectionFactory orderConnectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer ) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); configurer.configure(factory, orderConnectionFactory); factory.setPubSubDomain(true); factory.setSessionTransacted(true); return factory; } }
- 业务使用时,按需指定对应的Bean即可,例如多主题监听示例:
// 注入第一套JmsTemplate发送消息 @Autowired @Qualifier("priceListJmsTemplate") private JmsTemplate priceListJmsTemplate; // 监听第一套Service Bus的主题 @JmsListener(destination = "price-list-topic", containerFactory = "priceListJmsListenerFactory") public void consumePriceListMsg(String payload) { // 业务处理逻辑 } // 监听第二套Service Bus的主题 @JmsListener(destination = "order-topic", containerFactory = "orderJmsListenerFactory") public void consumeOrderMsg(String payload) { // 业务处理逻辑 }
提示:如果使用Spring Boot 2.x版本,将上述代码中
jakarta.jms.ConnectionFactory替换为javax.jms.ConnectionFactory即可,其余逻辑不变。
内容的提问来源于stack exchange,提问作者snake_404
相关产品推荐
相关产品推荐

