spring boot configure multiple ActiveMQ instances - spring

I have requirement to move messages from queues on one ActiveMQ instance to another ActiveMQ instance. Is there a way to connect to two different ActiveMQ instances using spring boot configuration?
Do I need to create multiple connectionFactories? If so then how does JmsTemplate know which ActiveMQ instance to connect to?
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(JMS_BROKER_URL);
}
Any help and code examples would be useful.
Thanks in advance.
GM

Additionally to the response of #Chris
You have to create different BrokerService instances using differents ports and create different ConnectionFactory to connect to each broker and create different JmsTemplate using these different factories to send messages to differents brokers.
For example :
import javax.jms.ConnectionFactory;
import javax.jms.QueueConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
#Configuration
public class ActiveMQConfigurationForJmsCamelRouteConsumeAndForward {
public static final String LOCAL_Q = "localQ";
public static final String REMOTE_Q = "remoteQ";
#Bean
public BrokerService broker() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:5671");
broker.setBrokerName("broker");
broker.setUseJmx(false);
return broker;
}
#Bean
public BrokerService broker2() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:5672");
broker.setBrokerName("broker2");
broker.setUseJmx(false);
return broker;
}
#Bean
#Primary
public ConnectionFactory jmsConnectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:5671");
return connectionFactory;
}
#Bean
public QueueConnectionFactory jmsConnectionFactory2() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:5672");
return connectionFactory;
}
#Bean
#Primary
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(jmsConnectionFactory());
jmsTemplate.setDefaultDestinationName(LOCAL_Q);
return jmsTemplate;
}
#Bean
public JmsTemplate jmsTemplate2() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(jmsConnectionFactory2());
jmsTemplate.setDefaultDestinationName(REMOTE_Q);
return jmsTemplate;
}
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory2(
#Qualifier("jmsConnectionFactory2") ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
}
To move messages from one AMQ instance to another instance you can use JmsBridgeConnectors :
Note that by the example below you cannot have multiple consumers on the queue from which you want to forward the messages because Camel or JmsBridgeConnectors consume the message and forward it. If you want a only copy of the message to be forwarded you have some solutions :
1- Convert your queue to a topic, manage the messages for offline consumers by a durable subscriptions or retroactive consumers.
2- convert your queue to a composite queue and use DestinationsInterceptors to copy messages to another queue.
3- use NetworkConnector for Networkof brokers
#Bean
public BrokerService broker() throws Exception {
final BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:5671");
SimpleJmsQueueConnector simpleJmsQueueConnector = new SimpleJmsQueueConnector();
OutboundQueueBridge bridge = new OutboundQueueBridge();
bridge.setLocalQueueName(LOCAL_Q);
bridge.setOutboundQueueName(REMOTE_Q);
OutboundQueueBridge[] outboundQueueBridges = new OutboundQueueBridge[] { bridge };
simpleJmsQueueConnector.getReconnectionPolicy().setMaxSendRetries(ReconnectionPolicy.INFINITE);
simpleJmsQueueConnector.setOutboundQueueBridges(outboundQueueBridges);
simpleJmsQueueConnector.setLocalQueueConnectionFactory((QueueConnectionFactory) jmsConnectionFactory());
simpleJmsQueueConnector.setOutboundQueueConnectionFactory(jmsConnectionFactory2());
JmsConnector[] jmsConnectors = new JmsConnector[] { simpleJmsQueueConnector };
broker.setJmsBridgeConnectors(jmsConnectors);
broker.setBrokerName("broker");
broker.setUseJmx(false);
return broker;
}
Or by using Camel like this below :
#Bean
public CamelContext camelContext() throws Exception {
CamelContext context = new DefaultCamelContext();
context.addComponent("inboundQueue", ActiveMQComponent.activeMQComponent("tcp://localhost:5671"));
context.addComponent("outboundQueue", ActiveMQComponent.activeMQComponent("tcp://localhost:5672"));
context.addRoutes(new RouteBuilder() {
public void configure() {
from("inboundQueue:queue:" + LOCAL_Q).to("outboundQueue:queue:" + REMOTE_Q);
}
});
context.start();
return context;
}
your Producer must be like this to use differents JmsTemplates :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
#Component
public class Producer implements CommandLineRunner {
#Autowired
private JmsTemplate jmsTemplate;
#Autowired
#Qualifier("jmsTemplate2")
private JmsTemplate jmsTemplate2;
#Override
public void run(String... args) throws Exception {
send("Sample message");
}
public void send(String msg) {
this.jmsTemplate.convertAndSend(ActiveMQConfigurationForJmsCamelRouteConsumeAndForward.LOCAL_Q, msg);
this.jmsTemplate2.convertAndSend(ActiveMQConfigurationForJmsCamelRouteConsumeAndForward.REMOTE_Q, msg);
}
}
and Consumer :
import javax.jms.Session;
import org.apache.activemq.ActiveMQSession;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
#Component
public class Consumer {
#JmsListener(destination = ActiveMQConfigurationForJmsCamelRouteConsumeAndForward.REMOTE_Q, containerFactory = "jmsListenerContainerFactory2")
public void receiveQueue(Session session, String text) {
System.out.println(((ActiveMQSession) session).getConnection().getBrokerInfo());
System.out.println(text);
}
}

You would need to instantiate multiple JmsTemplate instances as Beans in your application and then use a combination of #Qualifier and #Primary annotations to indicate which JmsTemplate instance should go where.
For example
#Bean("queue1")
#Primary
public JmsTemplate getQueue1(#Qualifier("connectionFactory1")ConnectionFactory factory...){
...
}
#Bean("queue2")
#Primary
public JmsTemplate getQueue2(#Qualifier("connectionFactory2")ConnectionFactory factory...){
...
}
...
#Autowired
#Qualifier("queue1")
private JmsTemplate queue1;
...
See here for more info.

You can use the Spring Boot default for the queue consumer
#JmsListener(destination = “queue.name")
public void consumer(String message) {
// consume the message
}
And for the producer you can create another JmsTemplate #Bean
#Bean
public JmsTemplate jmsTemplate() {
return new JmsTemplate(new ActiveMQConnectionFactory("tcp://localhost:5671"));
}

This way you can register as many brokers/listeners as you wish dynamically:
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.JmsListenerConfigurer;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerEndpointRegistrar;
import org.springframework.jms.config.SimpleJmsListenerEndpoint;
import javax.jms.Message;
import javax.jms.MessageListener;
#Configuration
public class CustomJmsConfigurer implements JmsListenerConfigurer {
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
ActiveMQConnectionFactory amqConnectionFactory = new ActiveMQConnectionFactory();
amqConnectionFactory.setBrokerURL("brokerUrl");
amqConnectionFactory.setUserName("user");
amqConnectionFactory.setPassword("password");
amqConnectionFactory.setExclusiveConsumer(true);
DefaultJmsListenerContainerFactory containerFactory = new DefaultJmsListenerContainerFactory();
containerFactory.setConnectionFactory(amqConnectionFactory);
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("someIdentifier");
endpoint.setDestination("queueName");
endpoint.setMessageListener(new MessageListener() {
#Override
public void onMessage(Message message) {
// Do your stuff
}
});
registrar.registerEndpoint(endpoint, containerFactory);
}
}

Related

MQTT and Spring Boot Integration - When Connection lost

I am trying to using Spring MQTT Integration to build a client that is subscribe to MQTT broker. The code works as expected, no issues. I am struggling configuring it so that when the connection is lost, it subscribes automatically. What is happening now, is that when it disconnects, the connection is established but no is not subscribed anymore to my topic.
What should I do to capture the event correctly, and resubscribe again when connection is lost?
Here is my configuration
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.ConsumerStopAction;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
#Configuration
public class MqttBeans {
Logger logger = LoggerFactory.getLogger(MqttBeans.class);
#Bean
public MqttConnectOptions mqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[] { "ssl://URL:8883" });
options.setUserName("ubidot_bridge");
String pass = "PASS";
options.setPassword(pass.toCharArray());
options.setCleanSession(false);
options.setAutomaticReconnect(true);
options.setConnectionTimeout(30);
options.setKeepAliveInterval(90);
options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
return options;
}
#Bean
public MqttPahoClientFactory mqttClientFactory(MqttConnectOptions options) {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions( options );
factory.setConsumerStopAction(ConsumerStopAction.UNSUBSCRIBE_NEVER);
logger.info("Reconnected to the broker");
return factory;
}
#Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
#Bean
public MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapterConfig(MqttConnectOptions options) {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("ubidot_bridge_in",
mqttClientFactory(options), "#");
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(2);
adapter.setOutputChannel(mqttInputChannel());
logger.info("Setting up inbound channel");
return adapter;
}
#Bean
public MessageProducer inbound(MqttPahoMessageDrivenChannelAdapter adapter) {
return adapter;
}
#Bean
#ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler() {
logger.info("Setting up msg receiver handler");
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
logger.info("Msg received .. Topic: " + topic);
logger.info("Payload " + message.getPayload());
System.out.println();
}
};
}
#Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound( MqttConnectOptions options ) {
// clientId is generated using a random number
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler("ubidot_bridge_out", mqttClientFactory(options));
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("#");
messageHandler.setDefaultRetained(false);
return messageHandler;
}
}
Thank you in advance for the help
T.

Spring-core with #RabbitListener annotation does not fire bounded method

I am trying to use rabbitmq #RabbitListener annotation in my methods so that whenever any message arrives, my method can get auto executed. Based on the official documentation provided here, I made the folowing config class
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class RabbitConfiguration {
public static final String QUEUE_NAME="myQueue";
public static final String EXCHANGE_NAME="my_EXCHANGE";
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setMaxConcurrentConsumers(5);
return factory;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory =
new CachingConnectionFactory("localhost");
return connectionFactory;
}
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory());
}
#Bean
public RabbitTemplate rabbitTemplate() {
return new RabbitTemplate(connectionFactory());
}
#Bean
public Queue myQueue() {
return new Queue(QUEUE_NAME);
}
#Bean
MessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
simpleMessageListenerContainer.setConnectionFactory(connectionFactory());
simpleMessageListenerContainer.setQueues(myQueue());
simpleMessageListenerContainer.setMessageListener(new RabbitMQListner());
return simpleMessageListenerContainer;
}
#Bean
FanoutExchange exchange() {
return new FanoutExchange(EXCHANGE_NAME);
}
#Bean
Binding binding(Queue queue, FanoutExchange exchange) {
return BindingBuilder.bind(queue).to(exchange);
}
#Bean
MessagingService messagingService(){
return new MessagingService();
}
}
Then from my service class I used like this:-
#Component
public class MessagingService {
public void send(String msg){
ApplicationContext context =
new AnnotationConfigApplicationContext(RabbitConfiguration.class);
AmqpTemplate template = context.getBean(AmqpTemplate.class);
template.convertAndSend(QUEUE_NAME,"Hello from template "+msg);
}
#RabbitListener(queues=QUEUE_NAME)
private void receiveMessage(String order) {
System.out.println("Hello"+order);
}
The send method is working fine. But the receiveMessage method does not print the expected output. It should print the message as soon as it arrives in the queue. Instead, When I tried to apply #EnableRabbit annotation in my configuration file, I got java.lang.ArrayStoreException exception on runtime. The method receiveMessage does not even get executed.
You can ignore typo.
You need a method annotated with #RabbitHandler as well.
Example :
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class Consumer {
#RabbitListener(queues = "myQueue")
#RabbitHandler
public void handle(String msg) {
System.out.println("RCVD :: " + msg);
}
}

Cannot publish Topic-message to two subscriber simlutaneously using ActiveMQ

I have referred to the SpringBoot application publish and read from ActiveMQ topic to, publish a Topic using ActiveMQ. I have created two receiver micro-services which reads message from the topic.I have also created rest endpoint to publish the Topic.However, I have to execute this rest end point two times to publish the message for two receivers 1). The first execution of rest endpoint will send message to Receiver1
2). The second execution of rest endpoint will send message to Receiver2
Hence 2 receivers could not read from the Topic simultaneously.
Here is my code.
PublisherApplication.java
package com.springboot;
//import statements
#SpringBootApplication
#EnableDiscoveryClient
#EnableJms
public class PublisherApplication {
#Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
//setPubSubDomain identifies Topic in ActiveMQ
factory.setPubSubDomain(true);
return factory;
}
public static void main(String[] args) {
SpringApplication.run(PublisherApplication.class, args);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
PublishMessage.java
[Rest-end point to publish Topic]
package com.springboot.controller;
//import statements
#RestController
#RequestMapping(path = "/schoolDashboard/topic")
class PublishMessage {
public static final String MAILBOX_TOPIC = "mailbox.topic";
#Autowired
private JmsTemplate jmsTemplate;
#GetMapping(path = "/sendEmail")
public void sendStudentById() throws Exception{
System.out.println("Anindya-TopicSendMessage.java :: Publishing Email sent....");
jmsTemplate.convertAndSend(MAILBOX_TOPIC, "Topic - Email Sent");
}
}
ReceiverApplication01
[Note - Receiver01 is first microservice]
package com.springboot;
//import statements
#SpringBootApplication
#EnableJms
public class ReceiverApplication01 {
#Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
//setPubSubDomain identifies Topic in ActiveMQ
factory.setPubSubDomain(true);
return factory;
}
public static void main(String[] args) {
SpringApplication.run(ReceiverApplication01.class, args);
}
}
TopicMesssgeReceiver01.java
[Receiver01 Read message from Topic]
package com.springboot.message;
//import statement
#Component
public class TopicMesssgeReceiver01 {
private final SimpleMessageConverter converter = new SimpleMessageConverter();
public static final String MAILBOX_TOPIC = "mailbox.topic";
#JmsListener(destination = MAILBOX_TOPIC, containerFactory = "topicListenerFactory")
public void receiveMessage(final Message message) throws JMSException{
System.out.println("Receiver01 <" + String.valueOf(this.converter.fromMessage(message)) + ">");
}
}
ReceiverApplication02
[Note:-Receiver02 is second microservice]
package com.springboot;
//import statement
#SpringBootApplication
#EnableJms
public class ReaderApplication02 {
#Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(true);
configurer.configure(factory, connectionFactory);
return factory;
}
public static void main(String[] args) {
SpringApplication.run(ReaderApplication02.class, args);
}
}
TopicMesssgeReceiver02
[Receiver02 Read message from Topic]
package com.springboot.message;
//import statement
#Component
public class TopicMesssgeReceiver02 {
private final SimpleMessageConverter converter = new SimpleMessageConverter();
public static final String MAILBOX_TOPIC = "mailbox.topic";
#JmsListener(destination = MAILBOX_TOPIC, containerFactory = "topicListenerFactory")
public void receiveMessage(final Message message) throws Exception{
System.out.println("Receiver02 <" + String.valueOf(this.converter.fromMessage(message)) + ">");
}
}
Thanks Naveen!! Finally, I am able to do it.
We have to set only setPubSubDomain(true); and spring-boot will take care all boiler-plate code. Now, two receiver Microservices can read message from Topic simultaneously Following are the code changes
PublishMessage.java
[Rest-end point to publish Topic]
package com.springboot.controller;
//import statements
#RestController
#RequestMapping(path = "/schoolDashboard/topic")
class PublishMessage {
public static final String MAILBOX_TOPIC = "mailbox.topic";
#Autowired
private JmsTemplate jmsTemplate;
#GetMapping(path = "/sendEmail")
public void sendStudentById() throws Exception{
System.out.println("Publisher :: Message sent...");
/* Added this statement. setPubSubDomain(true) identifies Topic in ActiveMQ */
jmsTemplate.setPubSubDomain(true);
jmsTemplate.convertAndSend(MAILBOX_TOPIC, "Topic - Email Sent");
}
}
ReceiverApplication02
[Note:-Receiver02 is second microservice]
package com.springboot;
//import statement
#SpringBootApplication
#EnableJms
public class ReaderApplication02 {
#Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
/* setPubSubDomain(true) should be placed after
* configuration of the specified jms listener container factory*/
factory.setPubSubDomain(true);
return factory;
}
public static void main(String[] args) {
SpringApplication.run(ReaderApplication02.class, args);
}
}

How to subcribe to Spring Boot JMS topic from other app

I have 2 applications, the first app starts a ActiveMQ broker ( https://spring.io/guides/gs/messaging-jms/ ).
At the second app I want to subcribe a topic from the first app.
How can I do this without starting a ActiveMQ Server?
Possible solution:
Server Application Project
import java.time.LocalDateTime;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.ui.ModelMap;
#SpringBootApplication
#EnableJms
#EnableScheduling
public class JsmServerApplication {
#Autowired
JmsTemplate jmsTemplate;
#Bean
public BrokerService broker() throws Exception {
BrokerService ret = new BrokerService();
ret.addConnector("tcp://0.0.0.0:4444"); // allow remote connections
ret.setBrokerName("primary-broker");
ret.setUseJmx(true);
return ret;
}
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory("tcp://localhost:4444");
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jacksonJmsMessageConverter());
return factory;
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
public static void main(String[] args) {
SpringApplication.run(JsmServerApplication.class, args);
}
#Scheduled(cron = "*/5 * * * * ?")
public void run() {
ModelMap msg = new ModelMap("now", LocalDateTime.now().toString());
System.out.println("Sending: " + msg);
jmsTemplate.convertAndSend("messages", msg);
}
}
Client Application Project
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
import org.springframework.ui.ModelMap;
#SpringBootApplication
#EnableJms
public class JsmClientApplication {
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory("tcp://localhost:4444");
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jacksonJmsMessageConverter());
return factory;
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
#JmsListener(destination = "messages", containerFactory = "jmsListenerContainerFactory")
public void msg(ModelMap msg) {
System.out.println(msg);
}
public static void main(String[] args) {
SpringApplication.run(JsmClientApplication.class, args);
}
}
Is it a correct approch?
Solved with this:
http://javasampleapproach.com/java-integration/activemq-work-spring-jms-activemq-topic-publisher-subcribers-pattern-using-springboot
You can use the MessageConsumer to consume the data like the code below
public static void main(String[] args) throws JMSException {
// Getting JMS connection from the server
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(url);
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("topic");
MessageConsumer consumer = session.createConsumer(topic);
MessageListener listner = new MessageListener() {
public void onMessage(Message message) {
try {
//do operations
} catch (JMSException e) {
}
}
};
consumer.setMessageListener(listner);
connection.close();
}
Since you are using the ActiveMQConnectionFactory, you can set the broker as below
BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:4444);
broker.setPersistent(false);
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");
If you do not have any restrictions in not using ActiveMQ, You can use Kafka for doing the same. Kafka provides you a highly scalable and distributed Message Bus with simple API.
https://kafka.apache.org/quickstart
I am not sure about the constraints but I just wanted to give you a feel of Kafka. However, the above code should help you in understanding the concept of subscribing and consuming messages from a topic.
See this answer for how to listen on a tcp port instead of the vm:// transport.

"Dispatcher has no subscribers" during startup

I am currently facing a problem which only happens sometimes and is very hard to reproduce. Therefore I have problems actually creating a compelling test case.
Our setup looks like this:
spring integration with spring boot
rabbitmq listener
custom bus which manages transaction aware messages
The setup is not the newest anymore and probably a lot of code could be replaced by more idiomatic spring wiring.
Though this is the exception I am getting when the service is started:
Dispatcher has no subscribers, failedMessage=GenericMessage [payload=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage#2ba0efb2, headers={id=f750a792-6b01-16d3-8206-6e553a03f8fa, type=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage, amqp_deliveryMode=PERSISTENT, timestamp=1505797680967}], failedMessage=GenericMessage [payload=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage#2ba0efb2, headers={id=f750a792-6b01-16d3-8206-6e553a03f8fa, type=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage, amqp_deliveryMode=PERSISTENT, timestamp=1505797680967}]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:93)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:423)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:373)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:105)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:143)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:135)
at org.springframework.integration.gateway.MessagingGatewaySupport.send(MessagingGatewaySupport.java:392)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:477)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.doInvoke(GatewayProxyFactoryBean.java:429)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invoke(GatewayProxyFactoryBean.java:420)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy223.send(Unknown Source)
at com.foobar.library.messaging.bus.TransactionAwareBus.afterCommit(TransactionAwareBus.java:50)
at org.springframework.transaction.support.TransactionSynchronizationUtils.invokeAfterCommit(TransactionSynchronizationUtils.java:133)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerAfterCommit(TransactionSynchronizationUtils.java:121)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerAfterCommit(AbstractPlatformTransactionManager.java:958)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:803)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at com.foobar.service.greencheckout.silo.domain.modifyingservice.TransitionService$$EnhancerBySpringCGLIB$$de36730c.execute(<generated>)
at com.foobar.service.greencheckout.silo.domain.statemachine.OrderFsm.invokeCreationalCommandMethod(OrderFsm.java:380)
at com.foobar.service.greencheckout.silo.domain.statemachine.OrderFsm.lambda$allowCreational$1(OrderFsm.java:345)
at akka.japi.pf.FSMStateFunctionBuilder$2.apply(FSMStateFunctionBuilder.java:80)
at akka.japi.pf.FSMStateFunctionBuilder$2.apply(FSMStateFunctionBuilder.java:77)
at akka.japi.pf.CaseStatement.apply(CaseStatements.scala:18)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at scala.PartialFunction$class.applyOrElse(PartialFunction.scala:123)
at akka.japi.pf.CaseStatement.applyOrElse(CaseStatements.scala:13)
at scala.PartialFunction$OrElse.apply(PartialFunction.scala:167)
at akka.actor.FSM$class.processEvent(FSM.scala:663)
at akka.actor.AbstractFSM.processEvent(AbstractFSM.scala:36)
at akka.actor.FSM$class.akka$actor$FSM$$processMsg(FSM.scala:657)
at akka.actor.FSM$$anonfun$receive$1.applyOrElse(FSM.scala:651)
at akka.actor.Actor$class.aroundReceive(Actor.scala:497)
at akka.actor.AbstractFSM.aroundReceive(AbstractFSM.scala:36)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:526)
at akka.actor.ActorCell.invoke(ActorCell.scala:495)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
at akka.dispatch.Mailbox.run(Mailbox.scala:224)
at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage#2ba0efb2, headers={id=f750a792-6b01-16d3-8206-6e553a03f8fa, type=com.foobar.service.greencheckout.message.GreenOrderPropertyAddedMessage, amqp_deliveryMode=PERSISTENT, timestamp=1505797680967}]
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:154)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:121)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:89)
... 58 more
The code to set this up looks like:
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.expression.ExpressionParser;
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.ExecutorChannel;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.messaging.SubscribableChannel;
import java.util.concurrent.Executors;
#Configuration
public class MessagingOutboundConfiguration
{
#Autowired
private AmqpTemplate amqpTemplate;
#Autowired
private Exchange publisherExchange;
#Autowired
private AmqpAdmin amqpAdmin;
#Autowired
private Exchange errorExchange;
#Autowired
private Queue errorQueue;
#Autowired
private BeanFactory beanFactory;
#Autowired
private ExpressionParser expressionParser;
#Autowired
private MessagingSettings messagingSettings;
#Bean
#DependsOn({"connectionFactory", "consumer"})
public AsynchronousBus asyncBus(SubscribableChannel amqpOutboundChannel) throws Exception
{
GatewayProxyFactoryBean factoryBean = new GatewayProxyFactoryBean(AsynchronousBus.class);
factoryBean.setBeanFactory(beanFactory);
factoryBean.setDefaultRequestChannel(amqpOutboundChannel);
factoryBean.afterPropertiesSet();
return (AsynchronousBus) factoryBean.getObject();
}
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public TransactionAwareBus transactionAwareBus()
{
return new TransactionAwareBus();
}
/**
* Channel from message bus to the outbound channel adapter
*/
#Bean
public SubscribableChannel amqpOutboundChannel(HeaderChannelInterceptor headerChannelInterceptor)
{
DirectChannel channel = new DirectChannel();
channel.setComponentName("amqp-outbound-channel");
channel.addInterceptor(headerChannelInterceptor);
return channel;
}
/**
* Outbound Channel Adapter
*/
#Bean
public AmqpOutboundEndpoint endpoint(SubscribableChannel confirmationChannel)
{
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
String[] allowedHeaders = new String[]{"*"};
headerMapper.setRequestHeaderNames(allowedHeaders);
headerMapper.setReplyHeaderNames(allowedHeaders);
AmqpOutboundEndpoint endpoint = new PhasedAmqpOutboundEndpoint(amqpTemplate);
endpoint.setHeaderMapper(headerMapper);
endpoint.setExchangeName(publisherExchange.getName());
endpoint.setRoutingKeyExpression(expressionParser.parseExpression("headers.type"));
endpoint.setConfirmCorrelationExpression(expressionParser.parseExpression("#this"));
if (messagingSettings.getPublisherConfirmations()) {
endpoint.setConfirmNackChannel(confirmationChannel);
endpoint.setConfirmAckChannel(new NullChannel());
}
return endpoint;
}
#Bean
public HeaderChannelInterceptor headerChannelInterceptor()
{
return new HeaderChannelInterceptor();
}
/**
* Shovels messages from channel to outbound channel adapter
*/
#Bean
public EventDrivenConsumer consumer(SubscribableChannel amqpOutboundChannel, AmqpOutboundEndpoint endpoint)
{
final EventDrivenConsumer consumer = new EventDrivenConsumer(amqpOutboundChannel, endpoint);
consumer.setBeanName("amqp-outbound-consumer");
return consumer;
}
/**
* Outbound Channel Adapter
*/
#Bean
public AmqpOutboundEndpoint errorEndpoint()
{
amqpAdmin.declareBinding(BindingBuilder.bind(errorQueue).to(errorExchange).with("#").noargs());
DefaultAmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
String[] allowedHeaders = new String[1];
allowedHeaders[0] = "*";
headerMapper.setRequestHeaderNames(allowedHeaders);
AmqpOutboundEndpoint endpoint = new PhasedAmqpOutboundEndpoint(amqpTemplate);
endpoint.setHeaderMapper(headerMapper);
endpoint.setExchangeName(errorExchange.getName());
endpoint.setRoutingKeyExpression(expressionParser.parseExpression("headers.routing"));
return endpoint;
}
#Bean
public EventDrivenConsumer errorConsumer(SubscribableChannel errorChannel, AmqpOutboundEndpoint errorEndpoint)
{
final EventDrivenConsumer consumer = new EventDrivenConsumer(errorChannel, errorEndpoint);
consumer.setBeanName("amqp-error-consumer");
return consumer;
}
#Bean
public SubscribableChannel errorChannel()
{
DirectChannel channel = new DirectChannel();
channel.setComponentName("amqp-error-channel");
return channel;
}
#Bean
public SubscribableChannel confirmationChannel()
{
ExecutorChannel channel = new ExecutorChannel(Executors.newSingleThreadExecutor());
channel.setComponentName("amqp-confirmation-channel");
return channel;
}
}
From my understanding:
This message normally means (especially in the shutdown case) that beans are not correctly wired or that the context does not know the best order for stopping them.
What I do not understand: What's wrong with my setup? :(
UPDATE:
As you can see the code is called from inside an actor. The actor system is configured this way:
#Bean
#DependsOn({"asyncBus", "prototypedTransactionAwareBus", "transactionAwareBus", "syncBus"})
public SpringActorSystem actorSystem() throws Exception
{
String profile = environment.getActiveProfiles()[0];
ActorSystem system = ActorSystem.create(akkaSettings.getSystemName(), akkaConfiguration(profile));
if ("testing".equals(profile)) {
Cluster.get(system).joinSeedNodes(Lists.newArrayList(Cluster.get(system).selfAddress()));
}
if ("kubernetes".equals(profile)) {
joinKubernetesSeedNodes(system);
}
SpringExtension.SpringExtProvider.get(system).initialize(context);
return new SpringActorSystem(system);
}
And the SpringActorSystem:
public class SpringActorSystem implements ApplicationListener<ContextClosedEvent>
{
private static final Logger LOGGER = LoggerFactory.getLogger(SpringActorSystem.class);
private final ActorSystem actorSystem;
public SpringActorSystem(ActorSystem actorSystem)
{
this.actorSystem = actorSystem;
}
public ActorSystem system()
{
return actorSystem;
}
#Override
public void onApplicationEvent(ContextClosedEvent event)
{
final Cluster cluster = Cluster.get(this.actorSystem);
cluster.leave(cluster.selfAddress());
LOGGER.info("SpringActorSystem shutdown initiated");
this.actorSystem.terminate();
try {
Await.result(actorSystem.whenTerminated(), Duration.create(10, TimeUnit.SECONDS));
} catch (Exception e) {
LOGGER.info("Exception while waiting for termination", e);
}
LOGGER.info("SpringActorSystem shutdown finished");
}
}
Well, I think
SpringExtension.SpringExtProvider.get(system).initialize(context);
in the initializing phase is really too early.
Consider to implement SmartLifecycle for your SpringActorSystem to mover that initialize(context) to the start() method.

Resources