Reputation: 11981
I have two Java standalone applications. I want to send message from one application and asynchronously receive message by two clients: one is in the same application as the sender. The other is in a different application. Both are connected with ActiveMQ broker. But I can only see that the first client got the message while the other didn't get the message. What is the general approach to connect two applications by JMS? I think I must have some unclear concept about JMS. I looked up around but couldn't figure out how to set up my Spring bean configuration file to publish/subscribe message between two Java applications.
Here is my sender's bean configuration file in the first application and also the first listener's bean which is in the same application:
<bean id="customerMessageSender" class="com.example.message.CustomerStatusSender">
<property name="jmsTemplate" ref="jsmTemplateBean" />
<property name="topic" ref="topicBean" />
</bean>
<bean id="jsmTemplateBean" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactoryBean"/>
<property name="pubSubDomain" value="true"/>
</bean>
<bean id="topicBean" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="CustomerStatusTopic" />
</bean>
<bean id="connectionFactoryBean" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<bean id="customerStatusListener" class="com.example.message.CustomerStatusListener" />
<bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactoryBean" />
<property name="destination" ref="topicBean" />
<property name="messageListener" ref="customerStatusListener" />
</bean>
Here is the bean configuration file for the second listener which is in a different application:
<bean id="topicBean" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="CustomerStatusTopic" />
</bean>
<bean id="connectionFactoryBean" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<bean id="anotherCustomerStatusListener" class="com.mydomain.jms.CustomerStatusMessageListener" />
<bean id="listenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactoryBean" />
<property name="destination" ref="topicBean" />
<property name="messageListener" ref="anotherCustomerStatusListener" />
</bean>
As you can see, both customerStatusListener
bean and anotherCustomerStatusListener
bean subscribe to the topicBean
. But only the first listener gets the message since it is in the same application as the sender while the second does not. What is the general principle to connect two Java applications by JMS so that message can be sent/received between two separate applications?
Edit: I can't add the following listener bean in the first XML file since the class CustomerStatusMessageListener
is in a different application therefore is not visible in the classpath of the first (sender's) application.
<bean id="anotherCustomerStatusListener" class="com.mydomain.jms.CustomerStatusMessageListener" />
Edit again: The following is the second listener in the second application which is separate from the first application. It contains a main
method to instantiate the listener bean (jms-beans.xml is the bean configuration file for the second listener as listed above).
public class CustomerStatusMessageListener implements MessageListener {
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
System.out.println("Subscriber 2 got you! The message is: "
+ ((TextMessage) message).getText());
} catch (JMSException ex) {
throw new RuntimeException(ex);
}
} else {
throw new IllegalArgumentException(
"Message must be of type TextMessage");
}
}
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("jms-beans.xml");
CustomerStatusMessageListener messageListener = (CustomerStatusMessageListener) context.getBean("anotherCustomerStatusListener");
context.close();
}
}
Upvotes: 2
Views: 7802
Reputation: 12224
Your understanding of JMS is correct. If you want two listeners to receive the same message, then a topic is the way to do it. It shouldn't matter if one listener is running in the same VM as the sender and the other one is not. Without seeing your code, your Spring configuration also looks correct. That leaves a few more things to check:
localhost
one place for one of the listeners and a different place for the other listener?Based on your comments, the second item is where you are having problems.
This blog post tells how to set up durable topics (and persist messages if you need messages to persist through broker restarts). Basically, add this configuration to your message listeners:
<property name="subscriptionDurable" value="true">
<property name="clientId" value="Some_unique_id">
<property name="durableSubscriptionName" value="Some_unique_id">
The client id and durable subscription name must be different for every subscriber, so your first listener would have something like:
<bean id="listenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactoryBean" />
<property name="destination" ref="topicBean" />
<property name="messageListener" ref="anotherCustomerStatusListener" />
<property name="subscriptionDurable" value="true">
<property name="clientId" value="listener1">
<property name="durableSubscriptionName" value="listener1">
</bean>
And second should have:
<bean id="listenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactoryBean" />
<property name="destination" ref="topicBean" />
<property name="messageListener" ref="anotherCustomerStatusListener" />
<property name="subscriptionDurable" value="true">
<property name="clientId" value="listener2">
<property name="durableSubscriptionName" value="listener2">
</bean>
Note that you must start your second listener at least once to register itself with the broker so the broker knows its clientId and will store messages for it, but you can then shut it down and start it later to get any messages it missed while it was down.
If your listener stays down for a long time on a high-volume system, the broker will store all messages for it, which might eventually fill the disk or slow the broker down. See the ActiveMQ documentation for automatically removing durable messages.
Upvotes: 6