JmsTemplate 无法使用 "AMQ219007: Cannot connect to server" 向 ActiveMQ Artemis 发送消息,但可以使用 JMS 类 发送消息

JmsTemplate fails to send message to ActiveMQ Artemis with "AMQ219007: Cannot connect to server" but can send message with JMS classes

我正在尝试使用 Spring 的 JmsTemplate 发送 JMS 消息,但它失败了,这是堆栈跟踪中显示的根本原因:

AMQ219007: Cannot connect to server

但是,我可以使用传递给 JmsTemplate 的同一连接工厂直接针对 JMS 类 发送消息编程。我确定我遗漏了一些明显的东西,但我就是想不通缺少什么配置才能使 JmsTemplate 正常工作。我认为 ActiveMQInitialContextFactory 的配置是正确的,因为我可以使用 javax.jms.* 类 发送消息。 FWIW,我已经尝试使用目标解析器配置 JmsTemplate,但这没有帮助。

配置如下:

<bean id="artemisJndiTemplate"
        class="org.springframework.jndi.JndiTemplate">
    <property name="environment">
        <props>
            <prop key="java.naming.factory.initial">org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory</prop>
            <prop key="java.naming.provider.url">(tcp://artemisServer1:61616,tcp://artemisServer2:61616)?user=$jmsArtemisSetup{user}&amp;password=$jmsArtemisSetup{password}&amp;jms.prefetchPolicy.all=1&amp;consumerWindowSize=0&amp;clientID=${NODENBRTAG}</prop>
        </props>
    </property>
</bean>

<!-- look up the JMS ConnectionFactory in JNDI -->
<bean id="artemisConnectionFactory"
        class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiTemplate" ref="artemisJndiTemplate" />
    <property name="jndiName" value="ConnectionFactory" />
</bean>

<bean id="qFromSvcToESB"
        class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="artemisConnectionFactory" />
    <property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE" />
    <property name="sessionTransacted" value="true" />
    <property name="defaultDestination">
        <bean class="org.springframework.jndi.JndiObjectFactoryBean">
            <property name="jndiTemplate" ref="artemisJndiTemplate" />
            <property name="jndiName" value="dynamicQueues/qFromSvcToESB" />
        </bean>
    </property>
</bean>

当我使用 JmsTemplate 发送消息时,我得到以下堆栈跟踪:

2021-08-18 10:06:31,796 [ERROR] [com.example.emailassistant.msghdlr.EmailAssistantMsgHdlr]:171  - 1629306356191 - jmsTemplate.send(...) failed.
org.springframework.jms.UncategorizedJmsException: Uncategorized exception occurred during JMS processing; nested exception is javax.jms.JMSException: Failed to create session factory; nested exception is ActiveMQNotConnectedException[errorType=NOT_CONNECTED message=AMQ219007: Cannot connect to server(s). Tried with all available servers.]
        at org.springframework.jms.support.JmsUtils.convertJmsAccessException(JmsUtils.java:311) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.support.JmsAccessor.convertJmsAccessException(JmsAccessor.java:185) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:507) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.send(JmsTemplate.java:576) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.send(JmsTemplate.java:567) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at com.example.emailassistant.msghdlr.EmailAssistantMsgHdlr.execute(EmailAssistantMsgHdlr.java:151) ~[com.example.emailassistant.msghdlr-0.0.1-SNAPSHOT.jar:?]
        at com.example.framework2.activemq.artemis.EpmServiceHandlerAdapter.dispatchToListener(EpmServiceHandlerAdapter.java:71) ~[com.example.framework2.activemq.artemis-5.1-SNAPSHOT.jar:?]
        at com.example.framework2.AbstractEpmMsgHdlrAdapter$EpmMessageListener.onMessage(AbstractEpmMsgHdlrAdapter.java:615) ~[com.example.framework2-5.1-SNAPSHOT.jar:?]
        at org.apache.activemq.artemis.jms.client.JMSMessageListenerWrapper.onMessage(JMSMessageListenerWrapper.java:110) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl.callOnMessage(ClientConsumerImpl.java:1031) ~[artemis-core-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl.access0(ClientConsumerImpl.java:50) ~[artemis-core-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl$Runner.run(ClientConsumerImpl.java:1154) ~[artemis-core-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.utils.actors.OrderedExecutor.doTask(OrderedExecutor.java:42) ~[artemis-commons-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.utils.actors.OrderedExecutor.doTask(OrderedExecutor.java:31) ~[artemis-commons-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.utils.actors.ProcessorBase.executePendingTasks(ProcessorBase.java:65) ~[artemis-commons-2.12.0.jar:2.12.0]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
        at org.apache.activemq.artemis.utils.ActiveMQThreadFactory.run(ActiveMQThreadFactory.java:118) [artemis-commons-2.12.0.jar:2.12.0]
Caused by: javax.jms.JMSException: Failed to create session factory
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnectionInternal(ActiveMQConnectionFactory.java:886) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnection(ActiveMQConnectionFactory.java:299) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnection(ActiveMQConnectionFactory.java:294) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.springframework.jms.support.JmsAccessor.createConnection(JmsAccessor.java:196) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:494) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        ... 15 more
Caused by: org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException: AMQ219007: Cannot connect to server(s). Tried with all available servers.
        at org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl.createSessionFactory(ServerLocatorImpl.java:690) ~[artemis-core-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnectionInternal(ActiveMQConnectionFactory.java:884) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnection(ActiveMQConnectionFactory.java:299) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnection(ActiveMQConnectionFactory.java:294) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.springframework.jms.support.JmsAccessor.createConnection(JmsAccessor.java:196) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:494) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        ... 15 more

此代码和配置以前使用 ActiveMQ“经典”连接工厂(即 org.apache.activemq.jndi.ActiveMQInitialContextFactory)。

这是我试过的目标解析器配置。我使用 <property name="destinationResolver" ref="artemisDestinationResolver" />:

将它添加到 JmsTemplate 配置中
<bean id="artemisDestinationResolver"
        class="org.springframework.jms.support.destination.JndiDestinationResolver">
    <property name="jndiTemplate" ref="amqJndiTemplate" />
    <property name="cache" value="true" />
</bean>

错误的根本原因是应用程序试图使用相同的客户端 ID 与 JMS 服务器建立两个 JMS 连接。 JMS 规范不允许这样做。

解决方案是使用连接池。通常,出于性能原因,应该包括连接池。在这种特殊情况下,应用程序启动,侦听一条消息,发送一条回复消息,然后关闭,因此未启用池。但是,在使用 artemis-jms-client 库时,这意味着使用相同的客户端 ID 创建了两个不同的 JMS 连接,从而导致失败。

以下 XML 使用池的配置允许服务正常工作:

<bean id="artemisJndiTemplate"
        class="org.springframework.jndi.JndiTemplate">
    <property name="environment">
        <props>
            <prop key="java.naming.factory.initial">org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory</prop>
            <prop key="java.naming.provider.url">(tcp://artemisServer1:61616,tcp://artemisServer1:61616)?user=$jmsArtemisSetup{user}&amp;password=$jmsArtemisSetup{password}&amp;consumerWindowSize=0&amp;clientID=${NODENBRTAG}</prop>
        </props>
    </property>
</bean>

<!-- look up the JMS ConnectionFactory in JNDI -->
<bean id="artemisConnectionFactoryBase"
        class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiTemplate" ref="artemisJndiTemplate" />
    <property name="jndiName" value="ConnectionFactory" />
</bean>

<!-- A cached connection to wrap the ActiveMQ connection -->
<bean id="artemisConnectionFactory"
    class="org.springframework.jms.connection.CachingConnectionFactory">
    <property name="targetConnectionFactory">
        <ref bean="artemisConnectionFactoryBase" />
    </property>
    <property name="sessionCacheSize">
        <value>100</value>
    </property>
</bean>

<bean id="replyJmsQueue"
        class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="artemisConnectionFactory" />
    <property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE" />
    <property name="sessionTransacted" value="true" />
    <property name="defaultDestination">
        <bean class="org.springframework.jndi.JndiObjectFactoryBean">
            <property name="jndiTemplate" ref="artemisJndiTemplate" />
            <property name="jndiName" value="dynamicQueues/replyJmsQueue" />
        </bean>
    </property>
</bean>

原始 JMS 代码和 JmsTemplate 都使用连接池,因此最终使用相同的连接。

顺便说一句,将 artemis-jms-client 库从 2.10.1 升级到 2.18.0 在堆栈跟踪中提供了比原始消息更有用的消息。

javax.jms.InvalidClientIDException: clientID=Node1 was already set into another connection

通过连接池,JMS 代码可与 artemis-jms-client 2.10.1 或 2.18.0 一起使用。