我何时应该关闭在无状态会话bean中创建的JMS连接?

时间:2013-11-04 16:18:05

标签: java ejb activemq

我有一个关于何时关闭在无状态会话bean(EJB)中创建的连接的一般性问题。连接是ActiveMQ,它们是在bean的构造函数中创建的。然后在方法中使用该连接,我想知道何时关闭此连接的适当时间/地点。

关闭连接的单独方法是否合适?是否必须由类使用bean调用?或者我应该简单地使用它关闭方法内的连接?我担心我可能会关闭连接然后重新使用该bean与现在关闭的连接,因为连接是在构造函数中打开的。以下是一些代码:

@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();
    }
}
}

如您所见,我目前有一个单独的close方法,应该在发送事件后由类使用bean调用。这是合法的还是要求麻烦?我对EJB缺乏经验,并对任何建议持开放态度。使用@EJB注释将bean注入到调用类中。

6 个答案:

答案 0 :(得分:2)

JMS API资源是JMS API连接和JMS API会话。通常,在不再使用JMS资源时释放它们非常重要。以下是一些有用的实践。

如果您希望仅在业务方法的生命周期内维护JMS API资源,最好关闭方法中finally块中的资源。 如果您希望在企业bean实例的生命周期内维护JMS API资源,最好使用组件的ejbCreate方法来创建资源并使用组件的ejbRemove方法来关闭资源。如果使用有状态会话Bean或实体bean并且希望将JMS API资源维护在缓存状态,则必须在ejbPassivate方法中关闭该资源并将其值设置为null,并且必须在ejbActivate中再次创建它方法

如果您使用消息驱动bean的ejbCreate方法来创建JMS API连接,则通常使用ejbRemove方法来关闭连接。

答案 1 :(得分:2)

Enterprise Bean有几个Lifecycle methods可用于此类活动:

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

@PreDestroy
private void onDestroy() {
    // housecleaning goes here
}
编辑 - 我发现你在这里以“原始”的方式使用ActiveMQ,可能是因为它不是你的appserver的原生JMS组件。但这导致了相当低级的东西,比如EJB代码中的代理URL。知道你为什么这样做可能会有所帮助,因为使用服务器的内置JMS基础设施应该会带来更好的解决方案,如果可能的话。

答案 2 :(得分:2)

在会话bean中存储JMS工厂配置选项不是很好。更好地存储在服务器级别,如下所述: https://www.initworks.com/wiki/display/public/JMS+messages+from+EJBs+on+GlassFish

服务器可以有连接池,它的性能很好。此外,服务器在需要时自动关闭连接,并且您没有在您的代码中处理它。

答案 3 :(得分:2)

更好的方法看起来像这个示例(JavaEE6 JMS样式),它从无状态EJB中向ObjectMessage发送Queue

@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资源应由您的应用程序服务器管理(除非您需要使用动态资源),因此将向您的应用程序公开@Resource注入。

Oracle不建议在无状态会话Bean中缓存JMS资源。创建JMSConnection对象非常便宜,因为它只是物理连接周围的薄包装器。 因此,可以不缓存它,或者在@PostConstruct / @PreDestroy方法中创建和删除它。事实上,我已经测试了利用EJB生命周期方法的方法,但只会遇到问题。

Session和Producer对象也不是线程安全的,每个线程需要创建一次。您可以通过将它们作为属性存储/缓存在SLSB中来解决线程问题。

所有这些都是保持简单并在生产者方法中本地利用资源的理由。

您需要考虑其他细节/注意事项,例如您可能需要的事务支持的级别和类型。或者处理“毒药信息”,但是用我的简单例子,我试图解决一些基本问题,我在这里看到许多答案。希望这会有所帮助。

答案 4 :(得分:2)

我看到接受的答案是建议每次都创建一个新连接,一个新会话和一个新生成器。它还声明Oracle表示创建新连接非常便宜。那不是真的。这取决于所使用的应用程序服务器。

例如,Weblogic documentation明确指出创建资源和会话的成本很高。我不知道ApacheMQ是否做得更好。

我建议您只创建一个连接并保留它。不要在EJB中保留引用,但要保留它。在单例中,每个应用程序范围,会话范围,您可以在任何地方。

如果您这样做,则还会遇到连接可能失败的其他问题。您将想知道您的缓存连接是否与服务器断开连接。自动重新连接功能不起作用。特别是在可自动迁移的JMS服务器的情况下。

您可以使用Connection.setExceptionListener(ExceptionListener)实现错误侦听器。

当调用onError(方法)时,您可以清除缓存。

答案 5 :(得分:2)

https://developer.jboss.org/wiki/ShouldICacheJMSConnectionsAndJMSSessions文章发布在JBOSS开发人员线程之一上。这清楚地说明了在JEE应用程序服务器中运行高速缓存连接和其他与JMS相关的资源的原因,这是JMS代码的反模式。

简而言之,JCA层池JMS连接和JMS会话。因此,当您调用createConnection()createSession()时,在大多数情况下,它并不是真正地调用实际的JMS实现来实际创建新的JMS连接或JMS会话,它只是从其自己的内部缓存中返回一个。因此,创建连接和会话并不昂贵,因此只需按以下步骤关闭JMS连接和会话即可

public void sendToExchange(ExchangeMessage exchangeMessage) {
        MessageProducer producer = null;
        try {
            connection = factory.createConnection();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            producer = session.createProducer(targetQueue);
            producer.setDisableMessageID(true);
            Message message = beanRegistryCore.getJmsExchangeMsgTransformerI().transformToJMSMessage(session, exchangeMessage);
            producer.send(message);
            producer.setTimeToLive(ttl);//default 15min
            logger.elkLog("78", "-1", LogEventsEnum.SENT_TO_EXCHANGE, exchangeMessage.toString());
        } catch (Exception e) {
            logger.error("LN:80", " Error when sending order to exchange:", e);
            throw new OMSCoreRuntimeException(e.getMessage(), e);
        } finally {
            try {
                if (producer != null)
                    producer.close();
            } catch (JMSException e) {
                logger.error("LN:87", "JMS producer close error:", e);
            }
            try {
                if (session != null)
                    session.close();
            } catch (JMSException e) {
                logger.error("LN:93", "JMS session close error:", e);
            }
 try {
                if (connection != null)
                    connection .close();
            } catch (JMSException e) {
                logger.error("LN:93", "JMS connection close error:", e);
            }
        }
    }