When should I close a JMS connection that was crea

2019-05-28 21:30发布

问题:

I have a general question about when to close connections that have been created within a stateless session bean (EJB). The connections are to ActiveMQ and they are created within the bean's constructor. The connection is then used within a method, and I'm wondering when the appropriate time/place to close this connection is.

Would it be appropriate to have a separate method for closing the connection, that must be called by the class using the bean? Or should I simply close the connection within the method using it? I am worried that I may close a connection then re-use that bean with a now-closed connection since the connection is opened in the constructor. Here's some code to bat around:

@Stateless
@LocalBean
public class SendEventsBean {


private static String brokerURL = ".......";
private static transient ConnectionFactory factory;
private transient Connection connection;
private transient Session session;
private transient MessageProducer producer;

public SendEventsBean() {
    factory = new ActiveMQConnectionFactory(brokerURL);
    try {
        connection = factory.createConnection();
        connection.start();
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        producer = session.createProducer(null);
    } catch (JMSException e) {
        e.printStackTrace();
    }
}

public void sendEvent(String id, String description, String area) {

    Destination destination;
    try {
        destination = session.createQueue("FWT." + "events");
        TextMessage message = session.createTextMessage(id + " " + description + " " + area);
        producer.send(destination, message);
    } catch (JMSException e) {
        e.printStackTrace();
    }   
}

public void close() throws JMSException {
    if (connection != null) {
        connection.close();
    }
}
}

As you can see, I currently have a separate close method that should be called by the class using the bean after sending an event. Is this legitimate, or asking for trouble? I am inexperienced with EJB and am open to any suggestions. The bean is injected into the calling class using the @EJB annotation.

回答1:

A better approach would look something like this example (JavaEE6 JMS style) which sends an ObjectMessage to a Queue from within a stateless EJB:

@Stateless
public class SendEventsBean {

  private static final Logger log = Logger.getLogger(SendEventsBean.class);

  @Resource(mappedName = "jms/MyConnectionFactory")
  private ConnectionFactory jmsConnectionFactory;

  @Resource(mappedName = "jms/myApp/MyQueue")
  private Queue queue;

  public void sendEvent() {
    Connection jmsConnection = null;
    try {
        connection = jmsConnectionFactory.createConnection();
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        MessageProducer producer = session.createProducer(queue);
        MyObj obj = new MyObj(1, "Foo");
        ObjectMessage myObjMsg = session.createObjectMessage(obj);
        producer.send(myObjMsg);
    } catch (JMSException jmxEx) {
        log.error("Couldn't send JMS message: ", jmsEx);
    }finally{
        if (jmsConnection != null) {
            try {
                jmsConnection.close();
            }catch(JMSException ex) {
               log.warn("Couldn't close JMSConnection: ", ex);
            }
        }
    }
  }

}

JMS resources should be administered by your Application server (unless you need to use dynamic ones) and as such will be exposed to your application for @Resource injection.

Caching JMS resources in your stateless session bean is not recommended by Oracle. It is very very cheap to create a JMSConnection object as it is only a thin wrapper around the physical connection. As such its OK not to cache it, or create and tear it down in @PostConstruct/@PreDestroymethods. In fact I've tested out the approach of leveraging the EJB lifecycle methods for this and only run into problems.

Also the Session and Producer objects are not thread safe, they need to be created once per thread. You could run into threading issues by storing/caching them in the SLSB as properties.

All these are reasons to keep it simple and leverage your resources locally in your producer method.

There are other details/considerations you will need to factor in, for example the level and type of transaction support you might need. Or the handling of 'poison messages', but with my simple example I am trying to address some of the basic issues I see with many of the answers here. Hope this helps.



回答2:

The JMS API resources are a JMS API connection and a JMS API session. In general, it is important to release JMS resources when they are no longer being used. Here are some useful practices to follow.

If you wish to maintain a JMS API resource only for the life span of a business method, it is a good idea to close the resource in a finally block within the method. If you would like to maintain a JMS API resource for the life span of an enterprise bean instance, it is a good idea to use the component's ejbCreate method to create the resource and to use the component's ejbRemove method to close the resource. If you use a stateful session bean or an entity bean and you wish to maintain the JMS API resource in a cached state, you must close the resource in the ejbPassivate method and set its value to null, and you must create it again in the ejbActivate method.

If you used the message-driven bean's ejbCreate method to create a JMS API connection, you ordinarily use the ejbRemove method to close the connection.



回答3:

Enterprise Beans have a couple of Lifecycle methods that you can use for such activities:

@PostConstruct
private void onCreate() {
    // basically what you have in your present constructor
}

@PreDestroy
private void onDestroy() {
    // housecleaning goes here
}

EDIT - I see that you're using ActiveMQ in a "raw" way here, probably because it's not your appserver's native JMS component. But this leads to pretty low-level stuff like the broker URL in your EJB code. It might be helpful to know why you're doing things this way as using your server's built-in JMS infrastructure should lead to an overall better solution, if that's possible of course.



回答4:

Store JMS factory configuration options in session bean is not very good. Better store on server level like described here: https://www.initworks.com/wiki/display/public/JMS+messages+from+EJBs+on+GlassFish

Server can have connection pool, its nice for performance. Also server close connections automatically when required, and you don't have handle it in yours code.



回答5:

I see the accepted answer is recommending to create a new connection, a new session and a new producer every time. And it also states that Oracle says it is very cheap to create new connections. That is not really true. It depends on the application server being used.

The Weblogic documentation, for example, states clearly that it is expensive to create resources and sessions. I don't know if ApacheMQ does better.

I would recommend you create only one connection and keep it. Don't keep the reference in the EJB, but do keep it. In a singleton, per application scope, session scope, wherever you want.

If you did that, you'd have an additional problem that the connection may fail. You will want to know if your cached connection gets disconnected from the server. The auto-reconnect feature does not work great. Especially in case of auto-migratable JMS Servers.

You can implement an error listener using Connection.setExceptionListener(ExceptionListener).

When the onError(method) gets called, you can clear you cache.