LumberSzquatch
LumberSzquatch

Reputation: 1083

Possible Strange Behaviour in JMS Config Class

This is a separate yet related question to my earlier post here: Safely Terminating a Spring JMS application

My JMS application that I have using spring boot processes everything correctly and shuts down with no errors. To get this to work I changed a bean from:

@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
    factory.setConnectionFactory(mqConnectionFactory());
    factory.setDestinationResolver(destinationResolver());
    factory.setConcurrency("1");
    factory.setErrorHandler(errorHandler());
    factory.setSessionTransacted(true);
    factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
    return factory;
}

To:

@Bean
    public DefaultMessageListenerContainer defaultMessageListenerContainer() {
        DefaultMessageListenerContainer jmsListenerContainer = new DefaultMessageListenerContainer();
        jmsListenerContainer.setConnectionFactory(mqConnectionFactory());
        jmsListenerContainer.setDestinationResolver(destinationResolver());
        jmsListenerContainer.setDestinationName(queueName);
        jmsListenerContainer.setConcurrency("1");
        jmsListenerContainer.setErrorHandler(errorHandler());
        jmsListenerContainer.setSessionTransacted(true);
        jmsListenerContainer.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
        jmsListenerContainer.setAutoStartup(false);
        return jmsListenerContainer;
    }

The problem with this is, I could have created just a "hotfix", as my knowledge about spring is little. the line in the changed bean jmsListenerContainer.setAutoStartup(false); was added when I stumbled upon this post: http://forum.spring.io/forum/spring-projects/integration/79176-illegalstateexception-no-message-listener-specified as without the autoStartup set to false I get this after every processed message in my logs:

java.lang.IllegalStateException: No message listener specified - see property 'messageListener'
    at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:691) [spring-jms-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:651) [spring-jms-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:315) [spring-jms-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:253) [spring-jms-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1150) [spring-jms-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1142) [spring-jms-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1039) [spring-jms-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at java.lang.Thread.run(Unknown Source) [?:1.8.0_74]

Everything still is processed and shut down correctly, I just see this in my logs. Not sure if there is a conflict in my config file or not that may be the root of this. I just want to make sure the changes won't cause problems, even though everything currently works as intended with no errors.

Lastly here is my entire configuration class:

@Configuration
@EnableJms
public class MQConfig {

    private static final Logger logger = LogManager.getLogger(MQConfig.class.getName());

    @Value("${mq.hostName}")
    String host;
    @Value("${mq.port}")
    Integer port;
    @Value("${mq.queueManager}")
    String queueManager;
    @Value("${mq.queueName}")
    String queueName;
    @Value("${mq.channel}")
    String channel;

    @Autowired
    Environment environment;

    @Bean
    public DefaultMessageListenerContainer defaultMessageListenerContainer() {
        DefaultMessageListenerContainer jmsListenerContainer = new DefaultMessageListenerContainer();
        jmsListenerContainer.setConnectionFactory(mqConnectionFactory());
        jmsListenerContainer.setDestinationResolver(destinationResolver());
        jmsListenerContainer.setDestinationName(queueName);
        jmsListenerContainer.setConcurrency("1");
        jmsListenerContainer.setErrorHandler(errorHandler());
        jmsListenerContainer.setSessionTransacted(true);
        jmsListenerContainer.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
        jmsListenerContainer.setAutoStartup(false);
        return jmsListenerContainer;
    }

    @Bean
    public MQConnectionFactory mqConnectionFactory() {
        MQConnectionFactory mqConnectionFactory = new MQConnectionFactory();

        try {
            mqConnectionFactory.setHostName(host);
            mqConnectionFactory.setPort(port);
            mqConnectionFactory.setQueueManager(queueManager);
            mqConnectionFactory.setTransportType(1);
        } catch (JMSException ex) {
            logger.error(ex.getStackTrace());
        }

        return mqConnectionFactory;
    }

    @Bean
    public DynamicDestinationResolver destinationResolver() {
        DynamicDestinationResolver destinationResolver = new DynamicDestinationResolver();

        try {
            Connection connection = mqConnectionFactory().createConnection();
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            destinationResolver.resolveDestinationName(session, queueName, false);
        } catch (JMSException ex) {
            logger.error(ex.getStackTrace());
        }

        return destinationResolver;
    }

    @Bean
    public MQQueue mqQueue() {
        MQQueue mqQueue = new MQQueue();

        try {
            mqQueue.setBaseQueueName(queueName);
            mqQueue.setBaseQueueManagerName(queueManager);
        } catch (JMSException ex) {
            logger.error(ex.getStackTrace());
        }

        return mqQueue;
    }

    @Bean
    public JmsErrorHandler errorHandler(){
        return new JmsErrorHandler();
    }

    @Bean
    public MQManager mqHoldManager(){
        return new MQManager(host, port, queueName,
                queueManager, channel);
    }

    @Bean
    public MQInitializer mqInitializer(){
        return new MQInitializer(environment);
    }


}

Thoughts? Is this messy?

EDIT: JMS Listener

@Component
public class MQConsumer {

    @Resource(name = "mqHoldManager")
    private MQManager mqHoldManager;
    @Resource(name = "defaultMessageListenerContainer")
    private DefaultMessageListenerContainer listenerContainer;

    @Autowired
    MQInitializer mqInitializer;    /* To ensure this bean executes before Listener Setup not necessarily for any particular usage*/

    final Logger logger = LogManager.getLogger(MQConsumer.class.getName());
    private static ReportManager reportManager = new ReportManager();
    private static boolean isFirstQueue = true;

    @JmsListener(destination = "${mq.queueName}")
    public void processOrder(String message) throws Exception {...}

Upvotes: 0

Views: 2939

Answers (1)

Gary Russell
Gary Russell

Reputation: 174769

I am confused.

DefaultJmsListenerContainerFactory is for use with annotated POJO methods:

@JmsListener(...)
public void foo(String bar) {...}

The factory is used to create the listener container for the method.

With your replacement configuration, I don't see you ever configuring a message listener into the DefaultMessageListenerContainer.

Usually you would have container.setMessageListner(myListener) where myListener is a MessageListener, a SessionAwareMessageListener or a MessageListenerAdapter that wraps a POJO listener.

Setting autoStartup to false and never starting the container does nothing except add the container bean to the context.

I don't see how you can ever get any messages with that configuration.

EDIT

Are you using Spring Boot? If so, it will create a default jmsListenerContainerFactory for you - that is my best guess.

In which case, your stop code is not really stopping the actual container - just the "dummy" one that was never started.

I suggest you give your @JmsListener an id, @Autowire the JmsListenerEndpointRegistry and call registry.getListenerContainer("myListener").stop().

@JmsListener(id = "myListener", destination = "${mq.queueName}")

Upvotes: 1

Related Questions