Spring Container hangs if ActiveMQ is not started

2019-07-04 06:49发布

问题:

I am currently using DefaultMessageListenerContainer to create listeners and JmsTemplate to send messages (producer) to queues.

Spring Configuration Snippet:

@Bean
public ActiveMQConnectionFactory connectionFactory() {
    ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);
    factory.setRedeliveryPolicy(desiredRedeliveryPolicy());

    return factory;
}

@Bean
public DefaultMessageListenerContainer requestMessageListenerContainer() {
    DefaultMessageListenerContainer requestMessageListenerContainer = new DefaultMessageListenerContainer();
    requestMessageListenerContainer.setConcurrentConsumers(noOfconcurrentConsumers);
    requestMessageListenerContainer.setConnectionFactory(connectionFactory());
    requestMessageListenerContainer.setDestinationName(requestQueueName);
    requestMessageListenerContainer.setMessageListener(requestMessageListener());
    requestMessageListenerContainer.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
    requestMessageListenerContainer.setSessionTransacted(false);

    return requestMessageListenerContainer;
}


@Bean
public JmsTemplate requestJmsTemplate() {
    JmsTemplate jmsTemplate = new JmsTemplate();
    jmsTemplate.setConnectionFactory(connectionFactory());
    jmsTemplate.setDefaultDestination(requestMqQueue());

    return jmsTemplate;
}

The issue that I am currently having is that my spring container loading process gets stuck if the ActiveMQ is not started before running the application.

I believe that the DefaultMessageListenerContainer and JmsTemplate are trying to create their connections and session to the ActiveMQConnectionFactory.

Outside of spring, I know if the activemq provided isnt running,

activeMQConnection.createSession()

is where the execution would get stuck. In a regular Java code I can timeout some long processing/potentially stuck process. But how can I do something like that in spring container?

I would like to know if there is any better way to declare these beans so that I would know if the activemq is stuck and the container doesn't get stuck?

Thanks in advance for any help.

Update 1:

I updated my Connection URL for Connection Factory and also added an ExceptionListener:

@Bean
public ActiveMQConnectionFactory connectionFactory() {
    ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(String.format("failover://(%s)?startupMaxReconnectAttempts=1&maxReconnectAttempts=2", ActiveMQConnectionFactory.DEFAULT_BROKER_BIND_URL));
    factory.setRedeliveryPolicy(desiredRedeliveryPolicy());
    factory.setExceptionListener(factoryExceptionListener());

    return factory;
}

public FactoryExceptionListener factoryExceptionListener(){
    return new FactoryExceptionListener();
}


public class FactoryExceptionListener implements ExceptionListener {
private static XLogger LOG =  XLoggerFactory.getXLogger(FactoryExceptionListener.class);

@Override
public void onException(JMSException exception) {
    LOG.error("Factory Exception Caught: "+exception.getMessage());
    System.exit(1);
}
}

Now a stupid Question.

I can see the error log getting printed, but the application isn't exiting after System.exit(1). Am I doing something wrong here?

This change helped with the blocking call not being blocking anymore. But I am not able to exit and that means application starts execution and throws a bunch of exceptions as activeMQ isnt available.

What I instead want it to (for now) crash the application. How can I do that?

Update 2: Instead of exiting out of the application (which is still not working - maybe something to do with the listener) I changed the Exception Listener to make a little more sense for my implementation. I am now trying to get the Broker Up if exception listener is triggered.

public void onException(JMSException exception) {
    LOG.error("Factory Exception Caught: "+exception.getMessage());

    try {
        BrokerService brokerService = new BrokerService();
        brokerService.addConnector("tcp://localhost:61616");
        brokerService.setDataDirectory("C:/temp/data");
        brokerService.setEnableStatistics(true);
        brokerService.setPersistent(true);
        brokerService.start();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

But I am getting following exception:

2014-07-16 10:24:35.009 [ActiveMQ Task-1] ERROR o.a.a.t.failover.FailoverTransport - Failed to connect to [tcp://localhost:61616] after: 1 attempt(s)
2014-07-16 10:24:35.012 [ActiveMQ Connection Executor: unconnected] ERROR c.b.s.o.b.m.FactoryExceptionListener - Factory Exception Caught: Connection refused: connect
Exception in thread "ActiveMQ Connection Executor: unconnected" java.lang.NoSuchMethodError: org.apache.activemq.transport.TransportFactory.bind(Lorg/apache/activemq/broker/BrokerService;Ljava/net/URI;)Lorg/apache/activemq/transport/TransportServer;
at org.apache.activemq.broker.BrokerService.createTransportConnector(BrokerService.java:2249)
at org.apache.activemq.broker.BrokerService.addConnector(BrokerService.java:291)
at org.apache.activemq.broker.BrokerService.addConnector(BrokerService.java:281)
at com.bhn.service.ordermgmt.bulkorder.mq.FactoryExceptionListener.onException(FactoryExceptionListener.java:19)
at org.apache.activemq.ActiveMQConnection$5.run(ActiveMQConnection.java:1998)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

However when I tried to execute the same code from another project. I was successfully able to get the BrokerService up and running. I am not sure what this error means and how to resolve it?

Update 3: Not sure what was wrong earlier but the same code is working now. Thanks for your help @Tim

回答1:

The reason for this is that the default URL that you are specifying is using the Failover transport. By default the transport will try to connect to the broker until you shut down your app. The createSession call is triggering the client to try and send its connection information request to the Broker but that can't happen until the client connects to the Broker.

One solution which was stated in the comments is to disable the auto startup feature so that the session create calls doesn't get executed on startup. You will however still run into the hang if you later if you trigger a session create while the broker is still down. You can configure the failover transport with a set number of connection attempts using the options shown on the failover transport page.