@JmsListener消息校验无效时如何阻止消息回滚?
解决JMS消息校验异常时阻止回滚的方案
当使用LocalValidatorFactoryBean和javax.validation注解在@JmsListener中校验消息载荷时,校验失败抛出的MethodArgumentNotValidException会触发容器自动回滚事务。由于ErrorHandler是回滚后才执行,无法阻止回滚,我们可以通过以下两种方案实现需求:
方案一:自定义消息处理方法工厂,捕获校验异常并提交事务
通过扩展DefaultMessageHandlerMethodFactory,在校验异常发生时手动获取Session并提交事务,绕过容器的自动回滚逻辑:
@EnableJms @Configuration public class ActiveMQConfig implements JmsListenerConfigurer { private static final Logger log = LoggerFactory.getLogger(ActiveMQConfig.class); @Override public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) { registrar.setMessageHandlerMethodFactory(customMethodFactory()); } @Bean public DefaultMessageHandlerMethodFactory customMethodFactory() { return new DefaultMessageHandlerMethodFactory() { @Override protected InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) { InvocableHandlerMethod originalHandler = super.createInvocableHandlerMethod(bean, method); return new InvocableHandlerMethod(bean, method) { @Override public Object invoke(Object... arguments) throws Exception { try { return originalHandler.invoke(arguments); } catch (ListenerExecutionFailedException e) { // 仅处理校验异常 if (e.getCause() instanceof MethodArgumentNotValidException) { // 遍历参数找到Session并提交 for (Object arg : arguments) { if (arg instanceof Session) { ((Session) arg).commit(); log.warn("消息校验失败,已提交事务阻止回滚: {}", e.getMessage()); return null; } } } // 其他异常正常抛出,交由原有逻辑处理 throw e; } } }; } }; } @Bean public Validator validator() { return new LocalValidatorFactoryBean(); } }
方案二:自定义监听容器,拦截校验异常并控制事务提交
修改自定义的AdtMessageListenerContainer,重写invokeListener方法,在校验异常发生时直接提交事务:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jms.listener.adapter.ListenerExecutionFailedException; import org.springframework.jms.listener.DefaultMessageListenerContainer; import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; public class AdtMessageListenerContainer extends DefaultMessageListenerContainer { private static final Logger log = LoggerFactory.getLogger(AdtMessageListenerContainer.class); @Override protected void invokeListener(Session session, Message message) throws JMSException { try { super.invokeListener(session, message); } catch (ListenerExecutionFailedException e) { // 判断是否为校验异常 if (e.getCause() instanceof MethodArgumentNotValidException) { session.commit(); log.warn("消息校验无效,已提交事务避免回滚: {}", e.getMessage()); } else { // 非校验异常按原有逻辑处理(触发回滚) throw e; } } } }
注意事项
- 两种方案都仅针对校验异常(
MethodArgumentNotValidException)执行提交操作,业务异常(如你的SpecificException)仍会按原有逻辑回滚。 - 由于容器配置了
setSessionTransacted(true),手动提交会覆盖容器的自动事务管理逻辑,需确保日志记录完整,便于后续排查无效消息。 - 建议将校验失败的消息转发到死信队列,避免消息丢失或重复消费。
内容的提问来源于stack exchange,提问作者SorryForAsking
相关产品推荐
相关产品推荐

