如何在AUTO_ACKNOWLEDGE JMS会话场景中模拟消息重新传递?

时间:2012-03-26 11:13:28

标签: spring junit jms activemq spring-jms

在下面的测试中,我试图模拟以下场景:

  1. 启动消息队列。
  2. 启动了在消息处理过程中失败的消费者。
  3. 生成一条消息。
  4. 消费者开始处理消息。
  5. 在处理期间,抛出异常以模拟消息处理失败。失败的消费者已经停止。
  6. 另一个消费者的目的是获取重新传递的消息。
  7. 但是我的测试失败了,并且消息没有重新传递给新的消费者。我会很感激任何暗示。

    MessageProcessingFailureAndReprocessingTest.java

    @ContextConfiguration(locations="com.prototypo.queue.MessageProcessingFailureAndReprocessingTest$ContextConfig",
            loader=JavaConfigContextLoader.class)
    public class MessageProcessingFailureAndReprocessingTest  extends AbstractJUnit4SpringContextTests {
        @Autowired
        private FailureReprocessTestScenario testScenario;
    
        @Before
        public void setUp() {
            testScenario.start();
        }
    
        @After
        public void tearDown() throws Exception {
            testScenario.stop();
        }
    
        @Test public void 
        should_reprocess_task_after_processing_failure() {
            try {
                Thread.sleep(20*1000);
    
                assertThat(testScenario.succeedingWorker.processedTasks, is(Arrays.asList(new String[]{
                        "task-1",
                })));
            } catch (InterruptedException e) {
                fail();
            }
        }
    
        @Configurable
        public static class FailureReprocessTestScenario {
            @Autowired
            public BrokerService broker;
    
            @Autowired
            public MockTaskProducer mockTaskProducer;
    
            @Autowired
            public FailingWorker failingWorker;
    
            @Autowired
            public SucceedingWorker succeedingWorker;
    
            @Autowired
            public TaskScheduler scheduler;
    
            public void start() {
                Date now = new Date();
                scheduler.schedule(new Runnable() {
                    public void run() { failingWorker.start(); }
                }, now);
    
                Date after1Seconds = new Date(now.getTime() + 1*1000);
                scheduler.schedule(new Runnable() {
                    public void run() { mockTaskProducer.produceTask(); }
                }, after1Seconds);
    
                Date after2Seconds = new Date(now.getTime() + 2*1000);
                scheduler.schedule(new Runnable() {
                    public void run() {
                        failingWorker.stop();
                        succeedingWorker.start();
                    }
                }, after2Seconds);
            }
    
            public void stop() throws Exception {
                succeedingWorker.stop();
                broker.stop();
            }
        }
    
        @Configuration
        @ImportResource(value={"classpath:applicationContext-jms.xml",
                "classpath:applicationContext-task.xml"})
        public static class ContextConfig {
            @Autowired
            private ConnectionFactory jmsFactory;
    
            @Bean
            public FailureReprocessTestScenario testScenario() {
                return new FailureReprocessTestScenario();
            }
    
            @Bean
            public MockTaskProducer mockTaskProducer() {
                return new MockTaskProducer();
            }
    
            @Bean
            public FailingWorker failingWorker() {
                TaskListener listener = new TaskListener();
                FailingWorker worker = new FailingWorker(listenerContainer(listener));
                listener.setProcessor(worker);
                return worker;
            }
    
            @Bean
            public SucceedingWorker succeedingWorker() {
                TaskListener listener = new TaskListener();
                SucceedingWorker worker = new SucceedingWorker(listenerContainer(listener));
                listener.setProcessor(worker);
                return worker;
            }
    
            private DefaultMessageListenerContainer listenerContainer(TaskListener listener) {
                DefaultMessageListenerContainer listenerContainer = new DefaultMessageListenerContainer();
                listenerContainer.setConnectionFactory(jmsFactory);
                listenerContainer.setDestinationName("tasksQueue");
                listenerContainer.setMessageListener(listener);
                listenerContainer.setAutoStartup(false);
                listenerContainer.initialize();
                return listenerContainer;
            }
    
        }
    
        public static class FailingWorker implements TaskProcessor {
            private Logger LOG = Logger.getLogger(FailingWorker.class.getName());
    
            private final DefaultMessageListenerContainer listenerContainer;
    
            public FailingWorker(DefaultMessageListenerContainer listenerContainer) {
                this.listenerContainer = listenerContainer;
            }
    
            public void start() {
                LOG.info("FailingWorker.start()");
                listenerContainer.start();
            }
    
            public void stop() {
                LOG.info("FailingWorker.stop()");
                listenerContainer.stop();
            }
    
            @Override
            public void processTask(Object task) {
                LOG.info("FailingWorker.processTask(" + task + ")");
                try {
                    Thread.sleep(1*1000);
                    throw Throwables.propagate(new Exception("Simulate task processing failure"));
                } catch (InterruptedException e) {
                    LOG.log(Level.SEVERE, "Unexpected interruption exception");
                }
            }
        }
    
        public static class SucceedingWorker implements TaskProcessor {
            private Logger LOG = Logger.getLogger(SucceedingWorker.class.getName());
    
            private final DefaultMessageListenerContainer listenerContainer;
    
            public final List<String> processedTasks;
    
            public SucceedingWorker(DefaultMessageListenerContainer listenerContainer) {
                this.listenerContainer = listenerContainer;
                this.processedTasks = new ArrayList<String>();
            }
    
            public void start() {
                LOG.info("SucceedingWorker.start()");
                listenerContainer.start();
            }
    
            public void stop() {
                LOG.info("SucceedingWorker.stop()");
                listenerContainer.stop();
            }
    
            @Override
            public void processTask(Object task) {
                LOG.info("SucceedingWorker.processTask(" + task + ")");
                try {
                    TextMessage taskText = (TextMessage) task;
                    processedTasks.add(taskText.getText());
                } catch (JMSException e) {
                    LOG.log(Level.SEVERE, "Unexpected exception during task processing");
                }
            }
        }
    
    }
    

    TaskListener.java

    public class TaskListener implements MessageListener {
    
        private TaskProcessor processor;
    
        @Override
        public void onMessage(Message message) {
            processor.processTask(message);
        }
    
        public void setProcessor(TaskProcessor processor) {
            this.processor = processor;
        }
    
    }
    

    MockTaskProducer.java

    @Configurable
    public class MockTaskProducer implements ApplicationContextAware {
        private Logger LOG = Logger.getLogger(MockTaskProducer.class.getName());
    
        @Autowired
        private JmsTemplate jmsTemplate;
    
        private Destination destination;
    
        private int taskCounter = 0;
    
        public void produceTask() {
            LOG.info("MockTaskProducer.produceTask(" + taskCounter + ")");
    
            taskCounter++;
    
            jmsTemplate.send(destination, new MessageCreator() {
                @Override
                public Message createMessage(Session session) throws JMSException {
                    TextMessage message = session.createTextMessage("task-" + taskCounter);
                    return message;
                }
            });
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext)
                throws BeansException {
            destination = applicationContext.getBean("tasksQueue", Destination.class);
        }
    }
    

1 个答案:

答案 0 :(得分:7)

显然我昨天看到的文档来源Creating Robust JMS Applications在某种程度上误导了我(或者我可能错误地理解了它)。特别是那段摘录:

  

在确认JMS消息之前,不会将其视为   成功消费。成功消费消息   通常分三个阶段进行。

     
      
  1. 客户收到消息。
  2.   
  3. 客户端处理邮件。
  4.   
  5. 消息已被确认。确认由JMS提供程序或客户端启动,具体取决于会话   确认模式。
  6.   

我假设 AUTO_ACKNOWLEDGE 完全相同 - 在侦听器方法返回结果后确认消息。但根据JMS规范,它有点不同,并且预期的Spring侦听器容器不会尝试改变JMS规范中的行为。这就是 AbstractMessageListenerContainer 的javadoc所说的 - 我强调了重要的句子:

  

侦听器容器提供以下消息确认   选项:

     
      
  • “sessionAcknowledgeMode”设置为“AUTO_ACKNOWLEDGE”(默认值):侦听器执行前的自动消息确认;如果发生异常,则不予重新发送。
  •   
  • “sessionAcknowledgeMode”设置为“CLIENT_ACKNOWLEDGE”:成功侦听器执行后自动确认消息;没有   如果发生异常,则重新发送。
  •   
  • “sessionAcknowledgeMode”设置为“DUPS_OK_ACKNOWLEDGE”:在侦听器执行期间或之后的延迟消息确认;潜在   如果发生异常,则重新发送。
  •   
  • “sessionTransacted”设置为“true”:成功侦听执行后的事务确认;如果发生异常,保证重新发送。
  •   

因此,我的解决方案的关键是listenerContainer.setSessionTransacted(true);

我遇到的另一个问题是JMS提供程序不断将失败的消息重新发送回处理消息期间失败的同一消费者。我不知道JMS规范是否规定了提供者在这种情况下应该做什么,但对我有用的是使用listenerContainer.shutdown();来断开失败的消费者并允许提供者重新发送消息并给其他消费者一个机会。