Spring Boot中JMS连接恢复后无法重新注册Bean的问题咨询
问题背景
使用DefaultMessageListenerContainer通过T3协议连接WebLogic JMS队列,服务正常时运行稳定,但T3地址宕机恢复后,Listener无法重新注册相关Bean,重写Listener也无法在应用启动后完成Bean注册。
原代码
@Bean public QueueConnectionFactory queueConnectionFactory() { Context m_context = getInitialContext(); QueueConnectionFactory queueConnectionFactory = new JMSConnectionFactory(); try { System.out.println("Connection Factory"); queueConnectionFactory = (QueueConnectionFactory) m_context .lookup("myconnectionfactory"); } catch (Exception e) { System.err.println("Exception Connection Factory goes down"); } return queueConnectionFactory; } @Bean public Context getInitialContext() { try { Properties h = new Properties(); System.out.println("getInitialContext "); h.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); h.put(Context.PROVIDER_URL, "t3://100.21.101.12:7001"); return new InitialContext(h); } catch (Exception e) { System.err.println("Error at getInitialContext"); } return null; } @Bean public Queue jmsQueue() { Context m_context = getInitialContext(); Queue jmsQueue = new MQQueue(); try { System.out.println("jmsQueue "); jmsQueue = (Queue) m_context.lookup("myqueue"); } catch (Exception e) { System.err.println("Error at JMS"); } return jmsQueue; } @Bean public DefaultMessageListenerContainer messageListener() { DefaultMessageListenerContainer listener = new DefaultMessageListenerContainer(); try { System.out.println("messageListener "); listener.setConcurrentConsumers("4"); listener.setConnectionFactory((ConnectionFactory) queueConnectionFactory()); listener.setDestination((Destination) jmsQueue()); listener.setMessageListener(queueListener()); } catch (Exception e) { System.out.println("Exception >>"+e); } return listener; }
错误日志
2022-08-05 15:16:24.615 ERROR 56480 --- [ssageListener-5] .b.e.s.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination from DefaultMessageListener'queue:///' - retrying using FixedBackOff{interval=5000, currentAttempts=52, maxAttempts=unlimited}. Cause: null
问题根源
当前代码中所有JMS相关Bean(ConnectionFactory、Queue)在Spring容器初始化时就完成了JNDI lookup,一旦T3服务宕机,这些Bean会持有失效的JNDI引用。即使T3服务恢复,Spring容器不会自动重新创建单例Bean,DefaultMessageListenerContainer的重试机制只能尝试重建连接,但使用的还是已失效的ConnectionFactory/Queue对象,导致无法恢复。
解决方案:动态化JNDI lookup,避免持有失效引用
通过自定义动态连接工厂和目标解析器,让每次需要连接或队列时重新执行JNDI lookup,确保拿到T3恢复后的有效对象,配合DefaultMessageListenerContainer的重试机制完成自动恢复。
1. 自定义动态QueueConnectionFactory
创建包装类,每次创建连接时重新执行JNDI lookup,获取最新的ConnectionFactory:
public class DynamicQueueConnectionFactory implements QueueConnectionFactory { @Override public QueueConnection createQueueConnection() throws JMSException { try { Context context = createInitialContext(); QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup("myconnectionfactory"); return factory.createQueueConnection(); } catch (NamingException e) { throw new JMSException("获取连接工厂失败", e); } } @Override public QueueConnection createQueueConnection(String userName, String password) throws JMSException { try { Context context = createInitialContext(); QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup("myconnectionfactory"); return factory.createQueueConnection(userName, password); } catch (NamingException e) { throw new JMSException("获取连接工厂失败", e); } } private Context createInitialContext() throws NamingException { Properties props = new Properties(); props.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); props.put(Context.PROVIDER_URL, "t3://100.21.101.12:7001"); return new InitialContext(props); } }
2. 自定义动态DestinationResolver
实现DestinationResolver,每次需要队列时重新执行JNDI lookup:
@Bean public DestinationResolver destinationResolver() { return new DynamicDestinationResolver() { @Override public Destination resolveDestinationName(Session session, String destinationName, boolean pubSubDomain) throws JMSException { try { Context context = createInitialContext(); return (Queue) context.lookup(destinationName); } catch (NamingException e) { throw new JMSException("获取队列失败", e); } } private Context createInitialContext() throws NamingException { Properties props = new Properties(); props.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); props.put(Context.PROVIDER_URL, "t3://100.21.101.12:7001"); return new InitialContext(props); } }; }
3. 修改DefaultMessageListenerContainer配置
不再直接注入初始化时的Queue Bean,而是通过destinationName和自定义的destinationResolver动态获取队列,同时使用动态连接工厂:
@Bean public QueueConnectionFactory queueConnectionFactory() { return new DynamicQueueConnectionFactory(); } @Bean public DefaultMessageListenerContainer messageListener() { DefaultMessageListenerContainer listener = new DefaultMessageListenerContainer(); listener.setConcurrentConsumers(4); listener.setConnectionFactory(queueConnectionFactory()); // 通过名称动态获取队列,配合自定义resolver listener.setDestinationName("myqueue"); listener.setDestinationResolver(destinationResolver()); listener.setMessageListener(queueListener()); // 明确配置重试间隔(默认已开启,可按需调整) listener.setRecoveryInterval(5000); // 允许刷新目标对象 listener.setRefreshDestination(true); return listener; }
4. 移除原有的单例InitialContext/Queue Bean
删除原有的getInitialContext()和jmsQueue() Bean定义,避免持有失效的JNDI引用。
原理说明
- 动态连接工厂每次创建连接时都会重新建立JNDI上下文并lookup连接工厂,确保T3恢复后能获取有效实例。
- 自定义DestinationResolver在每次需要队列时重新lookup,避免使用初始化时的失效队列对象。
DefaultMessageListenerContainer的重试机制会在连接失败后定期尝试重建连接,此时会调用动态工厂和解析器获取最新的JMS资源,从而自动恢复监听。
内容的提问来源于stack exchange,提问作者user3428736

