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();
}
}
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:
Are both listeners running on the same host? That is, is localhost one place for one of the listeners and a different place for the other listener?
Is your second listener running at the time the message was sent? If your second listener isn't active when the message is sent, it won't see if it starts later, unless you're using durable topics AND your subscriber has connected to the broker at least once.
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.
Related
I use the Listener to read the messages from MQ as mentioned in the link here
ExampleMessageListener :
This implements the onMessage method of Interface javax.jms.MessageListener
Listener :
<bean id="messageListener" class="sample.ExampleMessageListener" />
<bean id="msgListenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsConnectionFactory" />
<property name="destination" ref="jmsQueue" />
<property name="messageListener" ref="messageListener" />
</bean>
<jee:jndi-lookup id="jmsConnectionFactory" jndi-name="jms/CF1" />
<jee:jndi-lookup id="jmsQueue" jndi-name="jms/jmsQueue" />
I want to stop the listener when all the messages in MQ are read? (In other words, stop the listener when no meessages in MQ)
Any best way to identify the unavailability of messages in MQ?
using jms Browser ?
session.createBrowser(session.createQueue("Q"))
https://docs.oracle.com/javaee/7/api/javax/jms/Session.html#createBrowser-javax.jms.Queue-
https://docs.oracle.com/javaee/7/api/javax/jms/QueueBrowser.html#getEnumeration--
to start and stop the DMLC see here control-message-listener-container-to-stop-for-certain-period-and-start-again
I'm using ActiveMQ with the spring framework.
I have two consumers setup in the jms container. When I send 4 message to the queue, and some of the message are transferred to the "Dispatched Queue", because it takes a long time to the consumer to process the message.
I'm trying to find the way to prevent the message from going to the "Dispatched Queue", that is, I want them to be available to any consumer that is ready to consume the message.
I tried to set pre-fetch to 0, but it doesn't seem to work at all.
<bean id="prefetchPolicy" class="org.apache.activemq.ActiveMQPrefetchPolicy">
<property name="queuePrefetch" value="0"/>
</bean>
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<constructor-arg index="0" value="tcp://localhost:61616" />
<property name="prefetchPolicy" ref="prefetchPolicy"/>
</bean>
The following is the setup for my jms container:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="defaultDestination" />
<property name="messageListener" ref="messageListener" />
<property name="concurrentConsumers" value="2" />
</bean>
I found the problem. I had the same beans declared twice at two different places. The second bean that was loaded did not have pre-fetch set to 0 and therefore it didn't work out.
The above setup I posted works!
Thank you!
Message producer component has this :
session = connection.createSession(false,
Session.CLIENT_ACKNOWLEDGE);
producer = session.createProducer(session.createQueue(queueName));
I am using AWS SQS to send messages async
I was under the assumption that the message will be in the queue until it is acknowledged explicitly.(because of the client acknowledgement mode)
However it is removed off the queue.
Message consumer component:
I have written an async consumer to pull messages from the queue.
Here is the spring config file;
<bean id="ConnectionFactoryBuilder"
class="com.amazon.sqs.javamessaging.SQSConnectionFactory$Builder">
<property name="regionName" value="us-east-1" />
<property name="numberOfMessagesToPrefetch" value="1" />
<property name="awsCredentialsProvider" ref="CredentialsProviderBean" />
</bean>
<bean id="ConnectionFactory" class="com.amazon.sqs.javamessaging.SQSConnectionFactory"
factory-bean="ConnectionFactoryBuilder" factory-method="build" />
<bean id="Connection" class="javax.jms.Connection" factory-bean="ConnectionFactory"
factory-method="createConnection" init-method="start" destroy-method="close" />
<bean id="QueueName" class="java.lang.String">
<constructor-arg value="${sqs.queueName}" />
</bean>
<bean id="amazonMessageListener" class="com.myapp.AsyncMessageListener" />
<bean id="messageListener"
class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<property name="delegate" ref="amazonMessageListener" />
<property name="defaultListenerMethod" value="onMessage" />
<property name="messageConverter">
<null />
</property>
</bean>
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="ConnectionFactory" />
<property name="destinationName" ref="QueueName" />
<property name="messageListener" ref="messageListener" />
<property name="sessionAcknowledgeMode" value="2"/>
</bean>
i configured my consumer with client acknowledgemode using the property sessionAcknowledgeMode. But still the message is removed off the queue without the consumer explicitly acknowledging using message.acknowledge()
Per AbstractMessageListenerContainer (super class of DefaultMessageListenerContainer) doc:
"sessionAcknowledgeMode" set to "CLIENT_ACKNOWLEDGE": Automatic message acknowledgment after successful listener execution; best-effort redelivery in case of a user exception thrown as well as in case of other listener execution interruptions (such as the JVM dying).
So when using DefaultMessageListenerContainer, if onMessage() method in your listener returns without exception, spring acknowledges the message automatically.
I have a component which reads messages from a queue and meanwhile sends the processed messages to another queue. Therefore, this component is both a message consumer and producer. For configuring them, I need a connection factory for consuming and another connection factory for the producing. Here is part of the spring configuration.
<!-- Configuration for listener -->
<bean id="mdc.TargetConnectionFactory4Listener" class="com.tibco.tibjms.TibjmsConnectionFactory">
<property name="serverUrl" value="tcp://localhost:7222"/>
</bean>
<bean id="mdc.ConnectionFactory4Listener" class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter">
<property name="targetConnectionFactory" ref="mdc.TargetConnectionFactory"/>
<property name="username" value="admin" />
<property name="password" value="test" />
</bean>
<bean id="mdc.InputQueue" class="com.tibco.tibjms.TibjmsQueue">
<constructor-arg>
<value>INPUT_QUEUE</value>
</constructor-arg>
</bean>
<bean id="mdc.JmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="mdc.ConnectionFactory4Listener" />
<property name="destination" ref="mdc.InputQueue" />
<property name="messageListener" ref="mdc.MessageReceiver" />
......
</bean>
<!-- Configuration for sender -->
<bean id="mdc.TargetConnectionFactory4Sender" class="com.tibco.tibjms.TibjmsQueue">
<property name="serverUrl" value="tcp://localhost:7222"/>
</bean>
<bean id="mdc.CachingConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory" ref="mdc.TargetConnectionFactory4Sender" />
<property name="sessionCacheSize" value="50" />
</bean>
<bean id="mdc.OutputQueue" class="com.tibco.tibjms.TibjmsQueue">
<constructor-arg>
<value>DISCOVERY_QUEUE</value>
</constructor-arg>
</bean>
<bean id="mdc.JmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="mdc.CachingConnectionFactory" />
</bean>
<bean id="mdc.MessageReceiver" class="net.siemens.discovery.queue.QueueMessageListener">
<property name="jmsTemplate" ref="mdc.JmsTemplate" />
<property name="destination" ref="mdc.OutputQueue" />
......
</bean>
The two queues are running on the same EMS server. Some has opinions about this configurations: they can be configured by using only one ConnectionFactory and two instances are not necessary. However, if I use one ConnectionFactory instance, then the instance is used both in the DefaultMessageListenerContainer and the CachingConnectionFactory (further used in JmsTemplate). I don't know whether they have impact on each other.
It is perfectly normal to use a single connection factory; it is very unusual to use 2 factories in this case.
In fact, if you want to perform JmsTemplate operations on the container thread and you want the interactions to run in a transaction (sessionTransacted = true in the container), then you must use the same connection factory. This allows everything to roll back if there's an exception.
When using a caching connection factory in the listener container you should set the connection factory cacheConsumers to false. (See this answer for more information.
<bean id="myTopic" class="org.apache.activemq.command.ActiveMQTopic">
<property name="physicalName" value="feed.topic" />
</bean>
<bean id="myConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="failover:tcp://localhost:61616" />
</bean>
<bean id="myJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="myConnectionFactory" />
<property name="defaultDestination" ref="myTopic" />
</bean>
<bean id="sender" class="com.feed.publish.PublishMessages">
<property name="jmsTemplate" ref="myJmsTemplate" />
</bean>
I have the above set up using the spring framework that allows me to publish messages to a queue. However, if the activemq instance is terminated mid process, I would like it to write to disk/file the messages until a connection can be reestablished. I have found sample code off of the website of activemq however I am unsure how I integrate this in to my current setup
<amq:broker useJmx="true" persistent="true" brokerName="localhost">
<amq:persistenceAdapter>
<amq:kahaPersistenceAdapter directory="activemq-data"
maxDataFileLength="33554432" />
</amq:persistenceAdapter>
<amq:transportConnectors>
<amq:transportConnector name="vm" uri="vm://localhost" />
</amq:transportConnectors>
</amq:broker>
Can someone please tell me how I go about merging these two styles? Thanks
the AMQ persistenceAdapter configuration is to allow the AMQ Broker to persist messages to disk, not the client. If the broker connection is terminated, your client code should catch the exception, write the messages to disk and provide a means to replay them at a later time.
along these lines, I generally use Apache Camel's exception handling and file components to handle these type of scenarios...