Spring and AMQP RabbitMQ topic exchange not working - java

I'm trying to set up topic exchange on my spring app.
Here's my context configuration:
#Configuration
public class IntegrationConfig {
public final static String queueName = "my-queue";
#Bean
AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
#Bean
Queue queue() {
return new Queue(queueName);
}
#Bean
TopicExchange exchange() {
return new TopicExchange("my-exchange", false, true);
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("ru.interosite.*");
}
#Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(queueName);
container.setMessageListener(listenerAdapter);
return container;
}
#Bean
ImageUploadReceiver receiver() {
return new ImageUploadReceiver();
}
#Bean
MessageListenerAdapter listenerAdapter(ImageUploadReceiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
}
This is receiver class:
public class ImageUploadReceiver {
private CountDownLatch latch = new CountDownLatch(1);
public void receiveMessage(String message) {
System.out.println("Received ");
latch.countDown();
}
public CountDownLatch getLatch() {
return latch;
}
}
This is sender code:
#RequestMapping("/sendmessage")
#ResponseBody
public String sendMessage() {
rabbitTemplate.convertAndSend("ru.interosite.1", "ttt1233");
try {
imageUploadReceiver.getLatch().await(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Msg received";
}
So I'm sending message to topic exchange using binding key "ru.interosite.1" to the queue that was bound with pattern "ru.interosite.*". I used these key and pattern when tried sample from https://www.rabbitmq.com/tutorials/tutorial-five-java.html and they worked fine.
But inside String AMQP it does not work, i.e. receiver never gets called. It called only if binding key and pattern are completely the same as if I were using DirectExchange.
Am I missing something here?

You don't show the config for the RabbitTemplate, but I guess it is with default options.
To send a message to the my-exchange you must specify it directly:
rabbitTemplate.convertAndSend("my-exchange", "ru.interosite.1", "ttt1233");

You can also set the exchange in the rabbit template like this:
#Configuration
public class IntegrationConfig {
// ... as above
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setExchange("my-exchange");
return template;
}
}
So you can send the message with:
public class MyController {
#Autowired
RabbitTemplate rabbitTemplate;
#RequestMapping("/sendmessage")
#ResponseBody
public String sendMessage() {
rabbitTemplate.convertAndSend("ru.interosite.1", "ttt1233");
// ... as above
}
}

Related

RabbitMq listener listening to two different message types on two different queues

My requirement is to be able to listen to rabbitMq messages on two different queues. One queue receives message with content_type = text/plain and my Spring boot listener method accepts String parameter. Second queue receives message with content_type = application/json and my listener method accepts parameter of my POJO 'User' type. I am sending messages using RabbitMQ web portal. I am not able to successfully listen to both types of messages in the same spring boot listener application. Please help me to succeed in listening to both types of messages on two queues.Below is my listener class and configuraiton class code snippet.
Listener class:
#Component
public class EventListener {
public void processFirstQueue(String message) {
try {
if (message != null) {
log.info("Received the message from Queue!");
service.process(message);
}
} catch (Exception e) {
log.error("Error occurred " + e);
}
}
public void processSecondQueue(User user) {
try {
if (user!= null) {
log.info("Received the message from Queue!");
service.processUser(user);
}
} catch (Exception e) {
log.error("Error occurred " + e);
}
}
}
RabbitMqConfig.java
public class RabbitMqConfig {
#Bean(name = "rabbitmq-server")
public CachingConnectionFactory getConnectionFactory() {
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
cachingConnectionFactory.setHost("localhost");
return cachingConnectionFactory;
}
#Bean
public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames("queue1", "queue2");
container.setMessageListener(listenerAdapter);
container.setMessageConverter(new Jackson2JsonMessageConverter());
return container;
}
#Bean
public MessageListenerAdapter listenerAdapter(OutgoingEventListener receiver) {
MessageListenerAdapter listener = new MessageListenerAdapter(receiver);
Map<String, String> queueToMethodName = new HashMap<>();
queueToMethodName.put("queue1", "processFirstQueue");
queueToMethodName.put("queue2", "processSecondQueue");
listener.setQueueOrTagToMethodName(queueToMethodName);
return listener;
}
#Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
#Bean
public RabbitTemplate getRabbitTemplate(ConnectionFactory connectionFactory) {
return new RabbitTemplate(connectionFactory);
}
}
I am able to post json message successfully to queue2 as seen in below screenshot.
But when I am posting content_type = text/plain to queue1 as seen in below screenshot, I am getting error in my Spring boot service saying content-type text is found but json is expected.
Try to add message converter for your consumer like that -
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class RabbitMqConfig {
#Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
#Bean
public SimpleRabbitListenerContainerFactory jsaFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setMessageConverter(jsonMessageConverter());
return factory;
}
}

ActiveMQ messages from the topic are not received

I am having a weird situation. I am listening to a topic
I am able to listen to the message as long as the server is up and running. But the soon I stop server and messages are arriving in the queue and when the server is up again I am not able to consume those messages.
#Configuration
public class ConnectionFactoryConfig {
#Value("${jsa.activemq.broker.url}")
String brokerUrl;
#Value("${jsa.activemq.borker.username}")
String userName;
#Value("${jsa.activemq.borker.password}")
String password;
/*
* Initial ConnectionFactory
*/
#Bean
public ConnectionFactory connectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(brokerUrl);
connectionFactory.setUserName(userName);
connectionFactory.setPassword(password);
return connectionFactory;
}
// #Bean // Serialize message content to json using TextMessage
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
// converter.setTypeIdPropertyName("_type");
return converter;
}
#Bean
public JmsListenerContainerFactory<?> jsaFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(true);
// factory.setMessageConverter(jacksonJmsMessageConverter());
configurer.configure(factory, connectionFactory);
factory.setSessionTransacted(true);
factory.setSubscriptionDurable(true);
factory.setClientId("TEST_CLIENT");
return factory;
}
}
Subscriber
#Component
public class JmsSubcriber {
#JmsListener(destination = "${jsa.activemq.topic}")
public void receive(String msg) {
System.out.println(new Date() + "::Recieved Message: " + msg);
}
}
Property files
jsa.activemq.broker.url=tcp://localhost:61616
jsa.activemq.borker.username=admin jsa.activemq.borker.password=admin
jsa.activemq.topic=jsa-topic spring.jms.pub-sub-domain=true
See the messages in the queue I can se
But on starting the server I am not getting it.

RabbitMQ Exchange and Queue are not created automatically

I have created a new spring application which will push messages to a rabbitmq server.
My rabbitMQConfig java file looks like this :
#Configuration
public class RabbitMQConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQConfig.class);
#Value("${spring.rabbitmq.host}")
private String SPRING_RABBITMQ_HOST;
#Value("${spring.rabbitmq.port}")
private int SPRING_RABBITMQ_PORT;
#Value("${spring.rabbitmq.username}")
private String SPRING_RABBITMQ_USERNAME;
#Value("${spring.rabbitmq.password}")
private String SPRING_RABBITMQ_PASSWORD;
#Bean
public RabbitTemplate rabbitTemplate(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(SPRING_RABBITMQ_HOST,SPRING_RABBITMQ_PORT);
connectionFactory.setUsername(SPRING_RABBITMQ_USERNAME);
connectionFactory.setPassword(SPRING_RABBITMQ_PASSWORD);
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setExchange("my.controller.exchange");
rabbitTemplate.setRoutingKey("my.controller.key");
return rabbitTemplate;
}
#Bean
DirectExchange exchange() {
return new DirectExchange("my.controller.exchange", true, false);
}
#Bean
public Queue queue() {
return new Queue("my.controller", true);
}
#Bean
Binding exchangeBinding(DirectExchange exchange, Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("my.controller.key");
}
}
Here is how I push message to the queue :
#Service
public class RabbitPublisher {
#Autowired
private RabbitTemplate rabbitTemplate;
private static Logger LOGGER = Logger.getLogger(RabbitPublisher.class);
public Boolean pushToMyQueue(HashMap<String, Object> message) {
try {
rabbitTemplate.convertAndSend("my.controller.exchange","my.controller.key",message);
return true;
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("Error in pushing to my queue", e);
}
return false;
}
}
Since the exchange and queue are non-existent on the rabbitmq server, I expect them to be created automatically and message to be pushed. But it results in the following error :
ERROR 18198 --- [168.201.18:5672] o.s.a.r.c.CachingConnectionFactory :
Channel shutdown: channel error; protocol method: #method<channel.close>
(reply-code=404, reply-text=NOT_FOUND - no exchange
'my.controller.exchange' in vhost '/', class-id=60, method-id=40)
When I create the exchange and queue and bind them manually on the server, a message gets pushed successfully.
Please let me know if I am missing something. Thanks.
You need to add a RabbitAdmin #Bean. The admin will declare the elements when a connection is first opened.
You have to add AmqpAdmin admin bean with your required connection factory as below:
#Bean(name = "pimAmqpAdmin")
public AmqpAdmin pimAmqpAdmin(#Qualifier("defaultConnectionFactory") ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}

Handle the exceptions in RabbitMQ Spring Boot Application

I am using Spring Boot 1.4.1-RELEASE and RabbitMQ 3.2.3. My Application class looks like this -
#SpringBootApplication
#EnableAutoConfiguration
public class EventStoreMessageDeliveryApplication {
public final static String queueName = "customer.default.queue"; // spring-boot
#Bean
Queue queue() {
return new Queue(queueName, true);
}
#Bean
FanoutExchange exchange() {
return new FanoutExchange("customer.events.fanout.exchange", true, false); // spring-boot-exchange
}
#Bean
Binding binding() {
return new Binding(queueName, Binding.DestinationType.QUEUE, "customer.events.fanout.exchange", "*.*", null);
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
connectionFactory.setPublisherConfirms(true);
return connectionFactory;
}
#Bean
SimpleMessageListenerContainer container(MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setQueueNames(queueName);
container.setMessageListener(listenerAdapter);
container.setRecoveryBackOff(new ExponentialBackOff(3000, 2));
return container;
}
#Bean
MessageListenerAdapter listenerAdapter(Receiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(EventStoreMessageDeliveryApplication.class, args);
}
}
And my listener class looks like -
#Component
public class Receiver {
private CountDownLatch latch = new CountDownLatch(1);
public void receiveMessage(String message) {
System.out.println("Received <" + message + ">");
// do something
latch.countDown();
}
public CountDownLatch getLatch() {
return latch;
}
}
I want to handle the exceptions like connection refused which may come when the broker is down. How can I handle such exceptions? I am not sure where I can get the handle for the exceptions.
You can create a SimpleRabbitListenerContainerFactory. This is basically a listener for events from RabbitConnectionFactory.
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setErrorHandler(rabbitErrorHandler());
return factory;
}
rabbitErrorHandler() can return a bean of implementation of org.springframework.util.ErrorHandler.
Reference docs
I have a suggestion and it could work out. Since you want to have an exception of connection refused against the RabbitMQ broker, it is up to the client to catch the exception.
In your example, which looks like the one from SpringIO docs, I would assume you could make the exception handling in the main (not recommended though):
#Component
public class Runner implements CommandLineRunner {
private final RabbitTemplate rabbitTemplate;
private final Receiver receiver;
public Runner(Receiver receiver, RabbitTemplate rabbitTemplate) {
this.receiver = receiver;
this.rabbitTemplate = rabbitTemplate;
}
#Override
public void run(String... args) throws Exception {
System.out.println("Sending message...");
try {
rabbitTemplate.convertAndSend(Application.topicExchangeName, "foo.bar.baz", "Hello from RabbitMQ!");
receiver.getLatch().await(10000, TimeUnit.MILLISECONDS);
}catch(AmqpException the_exception) {
System.out.printl("Connection refused. Problem thrown when trying to connecto the RabbitMQ");
}
}
}
The AmqpException comes from the docs of the convertAndSend() method, which is being thrown if something went bad. Here you can capture your own custom message.
I hope this is what you are looking for or atleast guides you the correct destination.
/A

Spring AMQP StatefulRetryOperationsInterceptor not used

I am trying configure spring amqp to only retry a message a defined amount of times. Currently a message that fails e.g. because of a DataIntegrityViolationException is redelivered indefinitely.
According to the documentation here I came up with the following configuration
#Bean
public StatefulRetryOperationsInterceptor statefulRetryOperationsInterceptor() {
return RetryInterceptorBuilder.stateful()
.backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval
.maxAttempts(3)
.messageKeyGenerator(message -> UUID.randomUUID().toString())
.build();
}
This does not seem to be applied - the messages are still tried indefinitely.
Feels like I am missing something here.
Here is my remaining configuration regarding AMQP:
#Bean
Queue testEventSubscriberQueue() {
final boolean durable = true;
return new Queue("testEventSubscriberQueue", durable);
}
#Bean
Binding binding(TopicExchange topicExchange) {
return BindingBuilder.bind(testEventSubscriberQueue()).to(topicExchange).with("payload.event-create");
}
#Bean
SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(testEventSubscriberQueue().getName());
container.setMessageListener(listenerAdapter);
container.setChannelTransacted(true);
return container;
}
#Bean
MessageListenerAdapter listenerAdapter(MessageConverter messageConverter, SubscriberHandler subscriberHandler) {
MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(subscriberHandler);
listenerAdapter.setMessageConverter(messageConverter);
return listenerAdapter;
}
#Bean
public MessageConverter messageConverter(ObjectMapper objectMapper) {
final Jackson2JsonMessageConverter jsonMessageConverter = new Jackson2JsonMessageConverter();
jsonMessageConverter.setJsonObjectMapper(objectMapper);
DefaultClassMapper defaultClassMapper = new DefaultClassMapper();
defaultClassMapper.setDefaultType(EventPayload.class);
jsonMessageConverter.setClassMapper(defaultClassMapper);
final ContentTypeDelegatingMessageConverter messageConverter = new ContentTypeDelegatingMessageConverter(jsonMessageConverter);
messageConverter.addDelgate(MessageProperties.CONTENT_TYPE_JSON, jsonMessageConverter);
return messageConverter;
}
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
//rabbitTemplate.setChannelTransacted(true);
return rabbitTemplate;
}
#Bean
public TopicExchange testExchange() {
final boolean durable = true;
final boolean autoDelete = false;
return new TopicExchange(EXCHANGE_NAME, durable, autoDelete);
}
I am using spring-amqp 1.5.1.RELEASE.
Any help is appreciated.
You need to configure the container to add the interceptor to its advice chain...
container.setAdviceChain(new Advice[] { statefulRetryOperationsInterceptor() });

Categories

Resources