Is it possible to map a method in a spring managed bean to a topic subscription using spring messaging?
I've looked at the examples here: http://assets.spring.io/wp/WebSocketBlogPost.html
including the example stock application but it looks like all topic subscription is on the client side. Is it possible to also subscribe to topics on the server side?
You can use JMS to subscribe to the topic.
public class ExampleListener implements MessageListener {
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
System.out.println(((TextMessage) message).getText());
}
catch (JMSException ex) {
throw new RuntimeException(ex);
}
}
else {
throw new IllegalArgumentException("Message must be of type TextMessage");
}
}
}
<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="jmsexample.ExampleListener" />
<!-- and this is the message listener container -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener" />
</bean>
More here: http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/jms.html
Related
I have a simple Spring application for JMS Producer/Subscriber using ActiveMQ with below configuration :
Application Context xml :
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
<property name="userName" value="user" />
<property name="password" value="password" />
</bean>
<bean id="messageDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="messageQueue1" />
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE">
</property>
</bean>
<bean id="springJmsProducer" class="SpringJmsProducer">
<property name="destination" ref="messageDestination" />
<property name="jmsTemplate" ref="jmsTemplate" />
</bean>
<bean id="springJmsConsumer" class="SpringJmsConsumer">
<property name="destination" ref="messageDestination" />
<property name="jmsTemplate" ref="jmsTemplate" />
</bean>
and Below is Spring producer
public class SpringJmsProducer {
private JmsTemplate jmsTemplate;
private Destination destination;
public JmsTemplate getJmsTemplate() {
return jmsTemplate;
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public Destination getDestination() {
return destination;
}
public void setDestination(Destination destination) {
this.destination = destination;
}
public void sendMessage(final String msg) {
jmsTemplate.send(destination, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(msg);
}});
}
}
below is Spring Consumer:
public class SpringJmsConsumer {
private JmsTemplate jmsTemplate;
private Destination destination;
public JmsTemplate getJmsTemplate() {
return jmsTemplate;
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public Destination getDestination() {
return destination;
}
public void setDestination(Destination destination) {
this.destination = destination;
}
public String receiveMessage() throws JMSException {
TextMessage textMessage =(TextMessage) jmsTemplate.receive(destination);
return textMessage.getText();
}
}
Issue : When i start producer and post messages, and then i start consumer, Consumer is not reading old messages but only reading messages posted after consumer was started. Could anyone please help me how to make this durable subscriber so that messages in queue which are not acknowledged should be read by consumer and also i need to implement Synchronous Consumer not Asynchronous.
I have tried all possible solution but none is working. Any help is highly appreciated
if you want consumer receive messages sent to the topic before he starts you have 2 choice :
1. Use Activemq Retroactive Consumer
Background A retroactive consumer is just a regular JMS Topic consumer
who indicates that at the start of a subscription every attempt should
be used to go back in time and send any old messages (or the last
message sent on that topic) that the consumer may have missed.
See the Subscription Recovery Policy for more detail.
You mark a Consumer as being retroactive as follows:
topic = new ActiveMQTopic("TEST.Topic?consumer.retroactive=true");
http://activemq.apache.org/retroactive-consumer.html
2. Use Durable Subscribers :
Note that the Durable Subscriber receive messages sent to the topic before he starts at the 2nd run
http://activemq.apache.org/manage-durable-subscribers.html
This is possible with DefaultMessageListenerContainer Asynchronously
<bean id="jmsContainer" destroy-method="shutdown"
class="org.springframework.jms.listener.DefaultMessageListenerContainer" >
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="messageDestination" />
<property name="messageListener" ref="messageListenerAdapter" />
<property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE" />
<property name="subscriptionDurable" value="true" />
<property name="clientId" value="UniqueClientId" />
</bean>
<bean id="messageListenerAdapter"
class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg ref="springJmsConsumer" />
</bean>
<bean id="springJmsConsumer" class="SpringJmsConsumer">
</bean>
AND Update your consumer :
public class SpringJmsConsumer implements javax.jms.MessageListener {
public void onMessage(javax.jms.Message message) {
// treat message;
message.acknowledge();
}
}
UPDATE to use
if you want a Synchronous Durable Subscriber, an example
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
public class SpringJmsConsumer {
private Connection conn;
private TopicSubscriber topicSubscriber;
public SpringJmsConsumer(ConnectionFactory connectionFactory, Topic destination ) {
conn = connectionFactory.createConnection("user", "password");
Session session = conn.createSession(false, Session.CLIENT_ACKNOWLEDGE);
topicSubscriber = session.createDurableSubscriber(destination, "UniqueClientId");
conn.start();
}
public String receiveMessage() throws JMSException {
TextMessage textMessage = (TextMessage) topicSubscriber.receive();
return textMessage.getText();
}
}
And update springJmsConsumer
<bean id="springJmsConsumer" class="SpringJmsConsumer">
<constructor-arg ref="connectionFactory" />
<constructor-arg ref="messageDestination" />
</bean>
Note that connection failures are not managed by this code.
I`m using activemq 5.10 with spring 4.1.1.
I find a degrade problem with the response time of messages. After 4 days, the response time starts to grow up from 15ms to 200ms and more.
The app works fine with approximately 1000 messages per second and then all run slower.
Here is part of the xml beans:
<amq:systemUsage>
<amq:memoryUsage>
<amq:memoryUsage limit="512 mb">
</amq:memoryUsage>
</amq:memoryUsage>
<amq:storeUsage>
<amq:storeUsage limit="50 mb"></amq:storeUsage>
</amq:storeUsage>
<amq:tempUsage>
<amq:tempUsage limit="50 mb"></amq:tempUsage>
</amq:tempUsage>
</amq:systemUsage>
<amq:broker brokerName="myBroker" id="broker"
persistent="false" deleteAllMessagesOnStartup="true" enableStatistics="false"
useLoggingForShutdownErrors="true">
<amq:transportConnectors>
<amq:transportConnector
uri="nio://${Ip}:${Port}?jms.useAsyncSend=true?jms.useCompression=true"
disableAsyncDispatch="false" />
</amq:transportConnectors>
<amq:destinationPolicy>
<amq:policyMap>
<amq:policyEntries>
<amq:policyEntry queue=">" optimizedDispatch="true" />
</amq:policyEntries>
</amq:policyMap>
</amq:destinationPolicy>
</amq:broker>
<!-- A JMS connection factory for ActiveMQ -->
<bean id="ConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
p:brokerURL="nio://${Ip}:${Port}" />
<bean id="pooledJmsConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory"
destroy-method="stop">
<property name="connectionFactory" ref="ConnectionFactory" />
<property name="maxConnections" value="10" />
</bean>
<bean id="Listener" class="xx.com.xxx.MessageListener" />
<jms:listener-container container-type="default"
connection-factory="ConnectionFactory" acknowledge="auto">
<jms:listener destination="${QueueName}"
ref="sisBusMessageListener" method="onMessage" />
</jms:listener-container>
And here is the Java code:
public class MessageListener extends GenericMessageListener {
public void onMessage(Message request) {
MyExecutor mythread = new MyExecutor(request, new DateTime());
executor.execute(mythread );
}
}
public class MyExecutor {
public void init() {
try {
connectionFactory = ApplicationHelper.getBean("ConnectionFactory");
connectionFactory.setAlwaysSessionAsync(false);
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
} catch (JMSException e) {
logs...
}
}
public void returnMessage(Message request, Object responseFromExternalSystem) throws JMSException {
MapMessage response = session.createMapMessage();
response.setJMSCorrelationID(request.getJMSCorrelationID());
//code that set info on map message is here
replyProducer = session.createProducer(request.getJMSReplyTo());
replyProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
replyProducer.send(response);
}
}
The solution was re-use the connections and sessions creating only one instanace and creating an other instance in some cases(errors, desconnections...)
I want to create a sender to generate message and the send it to all consumers.
I am using topic, but something is wrong, if for example I have 3 consumers, only one takes the message in a random way.
I donĀ“t know what is wrog. Here is my server configuration
<amq:broker brokerName="granicaBroker" id="broker"
persistent="false" deleteAllMessagesOnStartup="true" enableStatistics="false"
useLoggingForShutdownErrors="true">
<amq:networkConnectors>
<amq:networkConnector name="linkToBrokerB"
uri="static:(tcp://xxx.xx.xxx.xx:61617)" networkTTL="3" duplex="true" />
</amq:networkConnectors>
<amq:transportConnectors>
<amq:transportConnector
uri="nio://xxx.xx.xxx.xx:61616?jms.useAsyncSend=true?jms.useCompression=true"
disableAsyncDispatch="false" />
</amq:transportConnectors>
</amq:broker>
<bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="JMS.TOPIC.NOTIFICATION" />
</bean>
<bean id="producerTemplate" class="org.springframework.jms.core.JmsTemplate"
p:connectionFactory-ref="connectionFactory"
p:defaultDestination-ref="destination" />
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
p:brokerURL="nio://xxx.xx.xxx.xx:61616" />
And my producer class(just the part to send the message)
#Autowired
protected JmsTemplate jmsTemplate;
final String text = applicationEvent.getMsg();
jmsTemplate.send(new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
TextMessage message = session.createTextMessage(text);
return message;
}
});
My client context configuration:
p:brokerURL="nio://xxx.xx.xxx.xx:61616" />
<bean id="simpleMessageListener" class="notifications.NotifierControllerImpl"/>
<jms:listener-container container-type="default"
connection-factory="connectionFactory" acknowledge="auto">
<jms:listener destination="JMS.TOPIC.NOTIFICATION" ref="simpleMessageListener"
method="onMessage" />
</jms:listener-container>
And the java client class
public class NotifierControllerImpl implements MessageListener{
#Override
public void onMessage(Message message) {
try {
if (message instanceof TextMessage) {
TextMessage tm = (TextMessage)message;
System.out.println(tm.getText());
}
} catch (JMSException e) {
System.out.println(e.toString());
}
}
}
The destination needs to be a topic not a queue; use ActiveMQTopic not ActiveMQQueue.
I change jms:listener-container part
Here is the code:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="simpleMessageListener" />
</bean>
And it works!
I am trying to test the rollback of a transaction that was initiated by EJB3 container, which involves the Spring JPA repository call and the message sender to the RabbitMQ using Spring AMQP integration. After the CMT rollback I see the DB transaction gets rolled back, but the message is getting delivered to the queue.
I am injecting the spring bean into the EJB that makes a call to the Rabbit template and it has #Transactional annotation. What I see is the TransactionInterceptor is committing the transaction after the Spring bean send message call. I was hoping it would delegate the commit to the container.
Any suggestions/workarounds are appreciated. I wasn't able to figure out how to initialize the TransactionSynchronizationManager without using the #Transactional annotation.
Here is the code that is committing the transaction when the spring bean proxy executes the TransactionInterceptor code:
ResourceHolderSynchronization
#Override
public void afterCommit()
{
if (!shouldReleaseBeforeCompletion()){ -- this method returns false
processResourceAfterCommit(this.resourceHolder); -- this is calling commitAll
}
}
ConnectionFactoryUtils/RabbitResourceSynchronization
#Override
protected boolean shouldReleaseBeforeCompletion() {
return !this.transacted; -- transacted is true (channelTransacted)
}
RabbitResourceHolder
public void commitAll() throws AmqpException {
try {
for (Channel channel : this.channels) {
if (deliveryTags.containsKey(channel)) {
for (Long deliveryTag : deliveryTags.get(channel)) {
channel.basicAck(deliveryTag, false);
}
}
channel.txCommit();
}
} catch (IOException e) {
throw new AmqpException("failed to commit RabbitMQ transaction", e);
}
}
Here is my spring configuration:
<tx:jta-transaction-manager/>
<tx:annotation-driven />
<rabbit:connection-factory id="rabbitConnectionFactory"
addresses="node01:5672,node02:5672" username="user.." password="pwd..." />
<bean id="rabbitTemplate"
class="org.arbfile.monitor.message.broker.api.rabbitmq.CustomRabbitTemplate" >
<property name="connectionFactory" ref="rabbitConnectionFactory" />
<property name="retryTemplate" ref="retryTemplate" />
<property name="exchange" value="user.example.exchange" />
<property name="queue" value="user.example.queue" />
<property name="routingKey" value="user.example.queue" />
<property name="channelTransacted" value="true" />
</bean>
Ideally after one queue completes its execution, another queue should start.
I am using Spring JMS. But at a time more than one queue able to execute by which data mismatch occurs.
So can anyone please tell how to restrict concurrent queue or unless first queue ends the 2nd queue cannot be started or all other queues are in waiting.
public class MyQueueListener implements MessageListener {
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage objectMessage = (ObjectMessage) message;
MyQueueObject obj= (MyQueueObject)objectMessage.getObject();
String productId = obj.getProductId();
IMyService myService;
myService.updateAllCustomerDetails(productId);
}
} catch (JMSException j) {
j.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}
}
<bean id="mySenderService" class="MySenderService">
<property name="jmsTemplate" ref="myjmsTemplate" />
<property name="queue" ref="myQueueDestination" />
</bean>
<bean id="myQueueDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>queue/myQueue</value>
</property>
<property name="resourceRef">
<value>true</value>
</property>
</bean>
<bean id="myQueueListener" class="MyQueueListener" ></bean>
<bean id="jmsImportContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="myQueueDestination" />
<property name="messageListener" ref="myQueueListener" />
</bean>
mySenderService.sendMessages();