I'm trying to publish a message to a queue and then have certain consumers consume it only if it contains a certain header and another consumer consume it if it contains another header.
What I've done so far is to setup a headers-exchange that routes messages to a certain queue only if it contains that header.
This is the config I'm using to setup the exchange and the queue and the listener:
<!-- Register Queue Listener Beans -->
<bean id="ActionMessageListener" class="com.mycee.Action" />
<!-- Register RabbitMQ Connections -->
<rabbit:connection-factory
id="connectionFactory"
port="${rabbit.port}"
virtual-host="${rabbit.virtual}"
host="${rabbit.host}"
username="${rabbit.username}"
password="${rabbit.password}"
connection-factory="nativeConnectionFactory" />
<!-- Register RabbitMQ Listeners -->
<rabbit:listener-container
connection-factory="connectionFactory"
channel-transacted="true"
requeue-rejected="true"
concurrency="${rabbit.consumers}">
<rabbit:listener queues="${queue.myqueue}" ref="ActionMessageListener" method="handle"/>
</rabbit:listener-container>
<!-- Setup RabbitMQ headers exchange -->
<rabbit:headers-exchange id="${exchange.myexchange}" name="${exchange.myexchange}">
<rabbit:bindings>
<rabbit:binding queue="${queue.myqueue}" key="action" value="action3" />
</rabbit:bindings>
</rabbit:headers-exchange>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:queue name="${queue.myqueue}" />
So I'm binding myqueue to myexchange using key of action and value of action3.
Now when I publish on the exchange:
the ChannelAwareMessageListener is consuming it even though the action was set to action1 instead of action3
public class Action implements ChannelAwareMessageListener {
#Override
public void onMessage(Message message, Channel channel) throws Exception {
System.out.println(message.toString());
}
}
Either I'm not using a headers-exchange correctly or I'm not configuring it correctly - any advice ?
It doesn't work that way; you need a separate queue for each consumer. See the tutorial.
When multiple consumers consume from the same queue they compete for all messages; you can't select messages on the consumer side; the "selection" is done by the exchange by routing messages to specific queue(s).
Related
Still new to spring and spring integration so please bear with me. =)
I have set up a client to connect to a remote server using TCP. The server sends a message as soon as a connection is established. Using ngrep I have verified that the connection is up and that the message is sent from the server.
By using the gateway interface "gw" I can successfully receive the message. However what I would like to do is to trigger the com.example.Client.onMessage method when a message is received. My understanding is that this should be possible using a ServiceActivator as shown below. Is this true or do I have to use my own dedicated thread doing a blocking receive? Thanks.
Configuration
<bean id="javaDeserializer"
class="org.springframework.integration.ip.tcp.serializer.ByteArrayLfSerializer" />
<int-ip:tcp-connection-factory id="client"
type="client" host="localhost" port="12000"
single-use="false" so-timeout="10000" so-keep-alive="true" deserializer="javaDeserializer"
serializer="javaSerializer"/>
<int:gateway id="gw" service-interface="com.example.Interface"
default-request-channel="input" default-reply-channel="replies" />
<int:channel id="input" />
<int:channel id="replies">
<int:queue />
</int:channel>
<int-ip:tcp-outbound-channel-adapter
id="outboundClient" channel="input" connection-factory="client" />
<int-ip:tcp-inbound-channel-adapter
id="inboundClient" channel="replies" connection-factory="client"
client-mode="true" retry-interval="10000" auto-startup="true" />
<int:service-activator input-channel="input" output-channel="replies" ref="com.example.Client" method="onMessage" />
ServiceActivator
#EnableIntegration
#IntegrationComponentScan
#MessageEndpoint
public class Client {
#ServiceActivator
public void onMessage(byte[] received) {
//Not called
}
}
#EnableIntegration and #IntegrationComponentScan must be configured on the #Configuration. The general Spring annotations configuration principles: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-java. Although having an XML config you don't need those annotations at all.
If you want to receive messages from TCP to the <service-activator>, you have to configure its input-channel to the replies.
Right now you have a mess in your config: several subscribers for the input channel. In this case they receive incoming messages via round-robin manner.
If I understand correctly you should remove all the <int:gateway> staff and just perform step #2. Although it isn't clear how you are going to send messages to the input channel...
I am using Spring 4.3.3 and I am trying to listen to a message where the destination type is topic.
I can achieve it in xml:
<jms:listener-container connection-factory="connectionFactory"
destination-type="topic"
message-converter="jackson2MessageConverter">
<jms:listener destination="test.topic" ref="jmsTopicMessageListener1" method="receiveMessage"/>
<jms:listener destination="test.topic" ref="jmsTopicMessageListener2" method="receiveMessage"/>
</jms:listener-container>
But I want to use #JmsListener. at the moment it is only working for queue destination like this:
#JmsListener(destination = "mailbox", containerFactory = "jmsListenerContainerFactory")
public void receiveMessage(DataObject dataObject) {
System.out.println("Received <" + dataObject.getName() + ">");
}
How do I listen to a topic with this annotation #JmsListener?
Thanks in advance.
UPDATE:
I have tried this in the config:
<bean id="topicJmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="connectionFactory" ref="cachingConnectionFactory"/>
<property name="destinationResolver" ref="jmsDestResolver"/>
<property name="concurrency" value="3-10"/>
<property name="messageConverter" ref="jackson2MessageConverter"/>
<property name="pubSubDomain" value="true"/>
</bean>
then this:
#JmsListener(destination = "test.topic", containerFactory = "topicJmsListenerContainerFactory")
public void receiveMessage(DataObject dataObject) {
System.out.println("Received <" + dataObject.getName() + ">" + 1);
}
I can get the values now but i get duplicates.
Since you have fixed your config (you are receiving messages that is) the only problem to solve is the fact that you are receiving duplicate messages. This is due to this piece of your config:
<property name="concurrency" value="3-10"/>
If you check the javadoc of DefaultMessageListenerContainer (which JmsListenerContainerFactory extends), you will find that:
...for a topic, you will typically stick with the default number of 1
consumer, otherwise you'd receive the same message multiple times on
the same node.
This is due to the architecture of the topic itself, multiple consumers will be treated as multiple subscribers who need to receive each message that was sent.
On the solution side:
You might need to reachitecture your code a bit. But most cases changing from Topic to VirtualTopic does the job. With VirtualTopics you can get "Queue-like" behaviour on the consumer side, meaning that messages will get consumed by a consumer, so multiple consumers cannot see the same message twice.
The other option of course is shifting the parallel processing workload to some other place and use a single consumer at the JMS entrypoint. Depending on the task at hand it might be a sufficient solution too.
I'm using simple Spring JmsTemplate to send messages onto a MQ.
<beans:bean id="myJMSTemplate" class="org.springframework.jms.core.JmsTemplate">
<beans:property name="connectionFactory">
<beans:ref bean="cachedConnectionFactory" />
</beans:property>
<beans:property name="pubSubDomain">
<beans:value>false</beans:value>
</beans:property>
<beans:property name="receiveTimeout">
<beans:value>1000</beans:value>
</beans:property>
</beans:bean>
<int-jms:outbound-channel-adapter id="sendMessageToAvengers" channel="antEventChannel" jms-template="myJMSTemplate" destination-name='com.marvel.avengers.antMan'/>
This works fine, however, My client application isn't able to process the message as the message format is in 'MQHRF2' by default.
How can I alter my MQ headers so as to send explicitly MQSTR format.
Thanks in advance.
You must set the property targetcllient=1 to send the msg as MQSTR.
To do this in sending part of java code, change the queue name as below
String senderQ = "queue:///MYQUEUENAME?targetClient=1";
jmsTemplate.send(senderQ, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
TextMessage message = session.createTextMessage(text);
message.setJMSReplyTo(replyToQ);
return message;
}
});
Alternatively you can try setting this in jmstemplate bean
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- set format to MQSTR with "targetClient=1" parameter -->
<property name="defaultDestinationName" value="queue:///default?targetClient=1" />
<property name="connectionFactory" ref="mqConnectionFactory" />
</bean>
The IBM MQ JMS interface has a property called TARGCLIENT which when set to the value MQ will not add the MQRFH2 header. I don't know whether the Spring interface allows it to be used.
Alternatively, the queue definition on the queue manager can be configured to remove the header for those applications that can't process it. This is a better way to remove the MQRFH2 header as it removes it at get time instead of at put time, thus meaning that if an application is able to process the MQRFH2 header, it is still there, but for those applications that cannot process it, it is removed for them.
To make the queue operate in this way, issue the following MQSC command on your queue manager:
ALTER QLOCAL(q-name) PROPCTL(NONE)
Additional Reading
Properties of IBM MQ classes for JMS objects > TARGCLIENT
PROPCTL queue options
DEFINE queues - see PROPCTL attribute
I am using Spring JMS with ActiveMQ as the broker and running the application on Tomcat.
I have one queue, let's say queue.a. In my web app, I already have a MessageListener running whenever I start my web app. The only thing is, I want to add some kind of queue consumer but synchronously. I already try using JmsTemplate etc. But when both of my consumer (listener async & consumer synchronous) is up and I trigger the .receive() method, the message sent to the queue always sucked up to the message listener that have been always online since the web app started. After the end of the timeout,the synchronous receiver did not consume any message at all.
But,when I comment out the messageListener, the synchronous customer run well.
I'm still a newbie,do any of you have any way to make what I want possible? Thanks! Sorry for my bad english :(
<bean id="someQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="TEST.QUEUE?consumer.priority=10" />
</bean>
and then, set it to your listener/receiver bean:
<bean id="someReceiver" class="blah.blah.SomeReceiver">
<property name="destination" ref="someQueue" />
<property name="jmsTemplate" ref="jmsTemplate" />
</bean>
Does this solve your problem?
I am using spring-jms with active mq.
I have a ErrorHandler listener set up, that receives a callback each time a jms message delivery fails - i.e. the method that processes the message throws an exception instead of returning gracefully.
However my jms is configured to retry several times until the jms delivery finally succeeds. And my callback is notified of all of the failures.
What I want is a listener that receives a notification only when all the retries finally fail. The motivation is to bring the issue up for the admin's attention. But I don't want spurious notifications in the admin's console.
<bean abstract="true" id="abstractDestinationListener"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory" />
<property name="errorHandler" ref="destinationErrorHandler"/>
<property name="sessionTransacted" value="true"/>
</bean>
What you might consider is using a per-destination dead-letter queue (individualDeadLetterStrategy - https://activemq.apache.org/message-redelivery-and-dlq-handling.html) for the destinations in question. When the maximum redelivery count has been hit, the message is moved out to the DLQ. You can then set up a consumer on that queue with a listener that emails the administrator.
Another way to do it might be to wrap your listener with a try-catch, and rethrow any exceptions only if message.getIntProperty("JMSXDeliveryCount") < MAX_REDELIVERY_COUNT, and email the admin otherwise. However, that option means placing your redelivery limit in two places - the broker config and the code.
if you need more fine-grained control/flexibility/routing with JMS messaging/handling, then you should just use Apache Camel's error handling...