Spring JmsListener捕获MQ提交异常与ExceptionListener透传MDC问题
问题1:捕获DefaultMessageListenerContainer提交异常
你当前使用CLIENT_ACKNOWLEDGE模式时,默认容器会在监听器方法无异常返回后自动执行消息确认,该阶段抛出的异常不会进入你配置的JMS ExceptionListener(仅处理连接级异常),可通过两种方案解决:
方案A:手动确认消息(最推荐,可同时解决MDC丢失问题)
关闭容器自动确认逻辑,在监听器方法内手动执行提交,提交异常直接在当前线程捕获:
@JmsListener(id="0", destination="DEV.QUEUE.1", containerFactory="containerFactory") public void listener(Message msg) { try { // 提取数据 MDC.put("key", value); // 用于审计 // 调用stub // 手动执行消息确认,提交异常直接在当前上下文捕获 msg.acknowledge(); } catch (JMSException e) { log.error("消息处理或提交失败", e); // 按需配置重试/死信队列逻辑,抛出异常会触发消息重发 throw new RuntimeException(e); } finally { MDC.clear(); // 必须清除,避免线程池复用导致MDC污染 } }
如果开启了事务模式,可注册事务同步回调捕获提交异常,同样可以直接获取当前线程MDC:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCompletion(int status) { if (status != STATUS_COMMITTED) { log.error("事务提交失败"); } } });
方案B:自定义ErrorHandler捕获容器级提交异常
如果需要保留容器自动提交逻辑,给容器工厂配置自定义ErrorHandler,提交阶段的异常会传递到该Handler:
@Bean public DefaultJmsListenerContainerFactory containerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(createConnectionFactory()); factory.setExceptionListener(exceptionListener()); factory.setSessionAcknowledgeMode(javax.jms.Session.CLIENT_ACKNOWLEDGE); // 配置自定义错误处理器 factory.setErrorHandler(customErrorHandler()); return factory; } @Bean public ErrorHandler customErrorHandler() { return throwable -> { log.error("容器处理消息异常", throwable); }; }
问题2:MDC值传递到异常监听器
MDC丢失的核心原因是ExceptionListener、ErrorHandler的执行线程和@JmsListener方法的执行线程不是同一个,MDC默认基于ThreadLocal存储,跨线程无法直接读取,可通过以下方案解决:
方案1:配合手动提交使用
就是上述问题1的方案A,提交逻辑和异常处理都在监听器线程执行,MDC天然存在,不需要额外做跨线程传递。
方案2:自定义TaskDecorator传递MDC
如果必须使用全局异常监听器,给监听器线程池配置TaskDecorator,在任务提交时把当前线程的MDC复制到执行线程:
public class MdcTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { Map<String, String> mdcContext = MDC.getCopyOfContextMap(); return () -> { try { if (mdcContext != null) { MDC.setContextMap(mdcContext); } runnable.run(); } finally { MDC.clear(); } }; } }
给容器工厂绑定带MDC装饰器的线程池:
@Bean public DefaultJmsListenerContainerFactory containerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(createConnectionFactory()); factory.setExceptionListener(exceptionListener()); factory.setSessionAcknowledgeMode(javax.jms.Session.CLIENT_ACKNOWLEDGE); // 配置带MDC传递的线程池 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(25); executor.setTaskDecorator(new MdcTaskDecorator()); executor.initialize(); factory.setTaskExecutor(executor); return factory; }
内容的提问来源于stack exchange,提问作者dauruy
相关产品推荐
相关产品推荐

