需要澄清 JMS 与 ActiveMQ bean/resource 配置

Need clarification on JMS vs ActiveMQ bean/resource configuration

在如何使用 JMS 资源以及在 @MessageDriven 注释上使用正确的 @ActivationConfigProperty 设置 activationConfig 方面似乎存在一些不一致。

首先,这是我的资源配置(glassfish-resources.xml,但可转换为其他部署描述符)。这与 ActiveMQ Resource Adapter:

一起应用于 Glassfish (asadmin add-resources glassfish-resources.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>

    <resource-adapter-config name="activemq-rar" 
                             thread-pool-ids="thread-pool-1" 
                             resource-adapter-name="activemq-rar">
        <property name="ServerUrl" value="tcp://localhost:61616"/>
        <property name="UserName" value="admin"/>
        <property name="Password" value="admin"/>
        <property name="UseInboundSession" value="false"/>
    </resource-adapter-config>
    <admin-object-resource enabled="true" 
                           jndi-name="jms/queue/myApp" 
                           object-type="user" 
                           res-adapter="activemq-rar" 
                           res-type="javax.jms.Queue">
        <description>MyApp JMS Queue</description>
        <property name="Name" value="myAppAMQ"/>
        <property name="PhysicalName" value="myAppAMQ"/>     
    </admin-object-resource>
    <connector-resource enabled="true" 
                        jndi-name="jms/factory/myApp" 
                        object-type="user" 
                        pool-name="jms/factoryPool/myApp">
        <description>MyApp Connection Factory</description>
        <property name="Name" value="myAppFactory"/>
    </connector-resource>
    <connector-connection-pool associate-with-thread="false" 
                               connection-creation-retry-attempts="0" 
                               connection-creation-retry-interval-in-seconds="10" 
                               connection-definition-name="javax.jms.QueueConnectionFactory" 
                               connection-leak-reclaim="false" 
                               connection-leak-timeout-in-seconds="0" 
                               fail-all-connections="false" 
                               idle-timeout-in-seconds="300" 
                               is-connection-validation-required="false" 
                               lazy-connection-association="false" 
                               lazy-connection-enlistment="false" 
                               match-connections="true" 
                               max-connection-usage-count="0" 
                               max-pool-size="32" 
                               max-wait-time-in-millis="60000" 
                               name="jms/factoryPool/myApp" 
                               ping="false" 
                               pool-resize-quantity="2" 
                               pooling="true" 
                               resource-adapter-name="activemq-rar" 
                               steady-pool-size="8" 
                               validate-atmost-once-period-in-seconds="0"/>
</resources>

这是我的消息提供者 bean。您会注意到找到了 JNDI 名称并且使用了 ActiveMQ 资源而没有错误,将消息发送到正确的队列:

@Stateless
@LocalBean
public class ServicesHandlerBean {

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

    public void sendJMSMessage(MessageConfig messageData) throws JMSException {
        Connection connection = null;
        Session session = null;
        try {
            connection = factory.createConnection();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageProducer messageProducer = session.createProducer(queue);
            messageProducer.send(createJMSMessage(session, messageData));
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (JMSException e) {
                    Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot close session", e);
                }
            }
            if (connection != null) {
                connection.close();
            }
        }
    }
}

当定义一个 @MessageDriven bean 时,混乱就开始了。以下使用 mappedName 抛出异常:

@MessageDriven(mappedName = "jms/queue/myApp")
public class MessageBean implements MessageListener

Warning: RAR8000 : The method setName is not present in the class : org.apache.activemq.command.ActiveMQQueue Warning: RAR7097: No setter method present for the property Name in the class org.apache.activemq.command.ActiveMQQueue Info: visiting unvisited references Info: visiting unvisited references Warning: RAR8501: Exception during endpoint activation for ra [ activemq-rar ], activationSpecClass [ org.apache.activemq.ra.ActiveMQActivationSpec ] : javax.resource.ResourceException: Unknown destination type: null Severe: MDB00017: [InvoiceProductionMessageBean]: Exception in creating message-driven bean container: [java.lang.Exception] Severe: java.lang.Exception

我不得不这样定义我的 MDB:

@MessageDriven(
        activationConfig = {
            @ActivationConfigProperty(propertyName = "connectionFactoryLookup", propertyValue = "jms/factory/myApp"),
            @ActivationConfigProperty(propertyName = "destination", propertyValue = "myAppAMQ"),
            @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = " JMSType = 'TypeA' "),
            @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
        }
)
public class MessageBean implements MessageListener

我需要提供一个 glassfish-ejb-jar.xml 告诉容器使用 ActiveMQ 资源,否则我会得到一个 java.lang.ClassCastException:

Warning: RAR8501: Exception during endpoint activation for ra [ jmsra ], activationSpecClass [ com.sun.messaging.jms.ra.ActivationSpec ] : java.lang.ClassCastException: org.apache.activemq.ra.ActiveMQConnectionFactory cannot be cast to com.sun.messaging.jms.ra.DirectConnectionFactory Severe: MDB00017: [MessageBean]: Exception in creating message-driven bean container: [java.lang.Exception] Severe: java.lang.Exception

glassfish-ejb-jar.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-ejb-jar PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 EJB 3.1//EN" "http://glassfish.org/dtds/glassfish-ejb-jar_3_1-1.dtd">
<glassfish-ejb-jar>
    <enterprise-beans>
        <ejb>
            <ejb-name>MessageBean</ejb-name>
            <mdb-resource-adapter>
                <resource-adapter-mid>activemq-rar</resource-adapter-mid>
            </mdb-resource-adapter>
        </ejb>
    </enterprise-beans>
</glassfish-ejb-jar>

因此,生产者使用资源的方式 (JNDI) 与消费者使用资源的方式 (XML + @ActivationConfigProperty) 之间似乎存在一些不一致。此外,EE7 ActivationConfigProperty 属性似乎不起作用。例如,使用 destinationLookup 不会查找目标,我不得不使用 ActiveMQ 的 destination 属性.

ActiveMQ lists the following Activation Spec Properties:

acknowledgeMode (The JMS Acknowledgement mode to use. Valid values are: Auto-acknowledge or Dups-ok-acknowledge)

clientId (The JMS Client ID to use (only really required for durable topics))

destinationType (The type of destination; a queue or topic)

destination (The destination name (queue or topic name))

enableBatch (Used to enable transaction batching for increased performance)

maxMessagesPerBatch (The number of messages per transaction batch)

maxMessagesPerSessions (This is actually the prefetch size for the subscription. (Yes, badly named).)

maxSessions (The maximum number of concurrent sessions to use)

messageSelector (The JMS Message Selector to use on the subscription to perform content based routing filtering the messages)

noLocal (Only required for topic subscriptions; indicates if locally published messages should be included in the subscription or not)

password (The password for the JMS connection)

subscriptionDurability (Whether or not a durable (topic) subscription is required. Valid values are: Durable or NonDurable)

subscriptionName (The name of the durable subscriber. Only used for durable topics and combined with the clientID to uniquely identify the durable topic subscription)

userName (The user for the JMS connection)

useRAManagedTransaction (Typically, a resource adapter delivers messages to an endpoint which is managed by a container. Normally, this container likes to be the one that wants to control the transaction that the inbound message is being delivered on. But sometimes, you want to deliver to a simpler container system that will not be controlling the inbound transaction. In these cases, if you set useRAManagedTransaction to true, the resource adapter will commit the transaction if no exception was generated from the MessageListener and rollback if an exception is thrown.)

initialRedeliveryDelay (The delay before redeliveries start. Also configurable on the ResourceAdapter)

maximumRedeliveries (The maximum number of redeliveries or -1 for no maximum. Also configurable on the ResourceAdapter)

redeliveryBackOffMultiplier (The multiplier to use if exponential back off is enabled. Also configurable on the ResourceAdapter)

redeliveryUseExponentialBackOff (To enable exponential backoff. Also configurable on the ResourceAdapter useJndi no false when true, use destination as a jndi name)

Java EE7 规格 lists the following Activation Spec Properties:

acknowledgeMode (This property is used to specify the JMS acknowledgement mode for the message delivery when bean-managed transaction demarcation is used. Its values are Auto_acknowledge or Dups_ok_acknowledge. If this property is not specified, JMS AUTO_ACKNOWLEDGE semantics are assumed.

messageSelector (This property is used to specify the JMS message selector to be used in determining which messages a JMS message driven bean is to receive)

destinationType (This property is used to specify whether the message driven bean is intended to be used with a queue or a topic. The value must be either javax.jms.Queue or javax.jms.Topic.)

destinationLookup (This property is used to specify the JMS queue or topic from which a JMS message-driven bean is to receive messages.)

connectionFactoryLookup (This property is used to specify the JMS connection factory that will be used to connect to the JMS provider from which a JMS message-driven bean is to receive messages.)

subscriptionDurability (If the message driven bean is intended to be used with a topic, this property may be used to indicate whether a durable or non-durable subscription should be used. The value of this property must be either Durable or NonDurable)

subscriptionName (This property is used to specify the name of the durable subscription if the message-driven bean is intended to be used with a Topic, and the bean provider has indicated that a durable subscription should be used.)

clientId (This property is used to specify the JMS client identifier that will be used when connecting to the JMS provider from which a JMS message-driven bean is to receive messages. If this property is not specified then the client identifier will be left unset.)

在只有@Inject 点和 jndi 查找的生产者和消费者中使用 ActiveMQ 资源的正确方法是什么?我想避免使用 glassfish-ejb-jar.xml 并使用 @ActivationConfigProperty.

定义队列名称

似乎所有服务器的做法都略有不同。

是的,每个应用程序服务器的处理方式都有些不同。 更重要的是,它们的不同之处不在于您如何配置它 - 这部分很简单,而是在您期望从 JMS 服务器获得 SLA(例如有序消息处理)时的运行时行为 - 即使在失败的情况下也是如此。

例如,如果您有一个关键业务流程,其中消息 2 只能在消息 1 之后处理。 而你的消息 1 失败了,你想重试,但是你也配置了 200 毫秒的重新传递延迟。 一些应用程序服务器,默认情况下会认为:消息 1 失败,我在 200 毫秒后重试,跳转到下一条消息...... kabum,业务流程已死,因为您刚刚违反了对有序消息消费的期望。

通常情况下,好的 JMS 服务器能够配置它以满足您所需的 SLA ......但这很棘手。

通常,您应该通过注释在 MDB 上配置任何跨多个应用程序服务器交叉工作的 属性。 通常,JNDI 命名可以工作——但这​​很棘手,因为 JNDI 高度依赖于容器。 属性例如: - 激活属性:destinationType = javax.jms.Topic

这个已经很标准了,直接注释即可。

但是当你遇到棘手的方面时,例如指定连接工厂以连接到目的地。 或者您应该允许 JMS 服务器一次批量读取 N 条消息,还是要一次强制读取一条消息,等等... 这高度依赖于您的容器,您将不希望通过 ejb 部署描述符的注释来配置它。

例如,在 weblogic 中,您需要使用:weblogic-ejb-jar.xml 微调 JNDI 名称以访问队列、max-beans-in-free-pool 等...

在使用 ActiveMQ 的 Wildfly 中,您可能希望使用: jboss-ejb3.xml

执行此操作的部署描述符。

因此,通过注释 - 您应该是跨容器的横切等效元数据的最大公分母。 在部署描述符中,您使用缺少的元数据丰富了配置。

优秀的应用程序服务器将始终执行合并过程,将 MDB 上的元数据与部署描述符上的元数据组合起来。 当发生冲突时,它们会接管部署描述符上的配置。

以此类推

那么,您确实需要在容器支持的部署描述符中调整 pert 容器。在您的 java 代码中,您应该只保留交叉兼容的元数据。

最后,在使用不同 JMS 服务器实现的不同应用服务器之间获取消息处理的准确 JMS 行为......非常棘手。

除非你有不关心有序处理的基本场景,否则你有多个 MDB 运行 在一个队列上并行,因为在关系之前没有发生......然后得到一个是微不足道的草率的配置工作。