Spring amqp converter issue using rabbit listener - rabbitmq

I think I am missing something here..I am trying to create simple rabbit listner which can accept custom object as message type. Now as per doc it says
In versions prior to 1.6, the type information to convert the JSON had to be provided in message headers, or a custom ClassMapper was required. Starting with version 1.6, if there are no type information headers, the type can be inferred from the target method arguments.
I am putting message manually in to queue using rabbit mq adm in dashboard,getting error like
Caused by: org.springframework.messaging.converter.MessageConversionException: Cannot convert from [[B] to [com.example.Customer] for GenericMessage [payload=byte[21], headers={amqp_receivedDeliveryMode=NON_PERSISTENT, amqp_receivedRoutingKey=customer, amqp_deliveryTag=1, amqp_consumerQueue=customer, amqp_redelivered=false, id=81e8a562-71aa-b430-df03-f60e6a37c5dc, amqp_consumerTag=amq.ctag-LQARUDrR6sUcn7FqAKKVDA, timestamp=1485635555742}]
My configuration:
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
connectionFactory.setUsername("test");
connectionFactory.setPassword("test1234");
connectionFactory.setVirtualHost("/");
return connectionFactory;
}
#Bean
RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
#Bean
public AmqpAdmin amqpAdmin() {
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory());
return rabbitAdmin;
}
#Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
Also question is with this exception message is not put back in the queue.
I am using spring boot 1.4 which brings amqp 1.6.1.
Edit1 : I added jackson converter as above (prob not required with spring boot) and given contenty type on rmq admin but still got below, as you can see above I am not configuring any listener container yet.
Caused by: org.springframework.messaging.converter.MessageConversionException: Cannot convert from [[B] to [com.example.Customer] for GenericMessage [payload=byte[21], headers={amqp_receivedDeliveryMode=NON_PERSISTENT, amqp_receivedRoutingKey=customer, content_type=application/json, amqp_deliveryTag=3, amqp_consumerQueue=customer, amqp_redelivered=false, id=7f84d49d-037a-9ea3-e936-ed5552d9f535, amqp_consumerTag=amq.ctag-YSemzbIW6Q8JGYUS70WWtA, timestamp=1485643437271}]

If you are using boot, you can simply add a Jackson2JsonMessageConverter #Bean to the configuration and it will be automatically wired into the listener (as long as it's the only converter). You need to set the content_type property to application/json if you are using the administration console to send the message.
Conversion errors are considered fatal by default because there is generally no reason to retry; otherwise they'd loop for ever.
EDIT
Here's a working boot app...
#SpringBootApplication
public class So41914665Application {
public static void main(String[] args) {
SpringApplication.run(So41914665Application.class, args);
}
#Bean
public Queue queue() {
return new Queue("foo", false, false, true);
}
#Bean
public Jackson2JsonMessageConverter converter() {
return new Jackson2JsonMessageConverter();
}
#RabbitListener(queues = "foo")
public void listen(Foo foo) {
System.out.println(foo);
}
public static class Foo {
public String bar;
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
#Override
public String toString() {
return "Foo [bar=" + this.bar + "]";
}
}
}
I sent this message
With this result:
2017-01-28 21:49:45.509 INFO 11453 --- [ main] com.example.So41914665Application : Started So41914665Application in 4.404 seconds (JVM running for 5.298)
Foo [bar=baz]
Boot will define an admin and template for you.

Ran into the same issue, turns out that, git stash/merge messed up with my config, I need to include this package again in my main again:
#SpringBootApplication(scanBasePackages = {
"com.example.amqp" // <- git merge messed this up
})
public class TeamActivityApplication {
public static void main(String[] args) {
SpringApplication.run(TeamActivityApplication.class, args);
}
}

Related

Unable to set WriteTimeout on reactor-netty version 0.9.10

I have written a Reactive API using Spring WebFlux version 2.3.0.RELEASE having reactor-netty version 0.9.10. As part of the API's SLA, I want to timeout the request if the Server takes more than the stipulated configured WriteTimeout.
Sharing the code snipped below where I have implemented a customizer for NettyReactiveWebServerFactory.
#Bean
public WebServerFactoryCustomizer serverFactoryCustomizer() {
return new NettyTimeoutCustomizer();
}
class NettyTimeoutCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
#Override
public void customize(NettyReactiveWebServerFactory factory) {
int connectionTimeout = 1000;
int writeTimeout = 1;
factory.addServerCustomizers(server -> server.tcpConfiguration(tcp ->
tcp.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
.doOnConnection(connection ->
connection.addHandlerLast(new WriteTimeoutHandler(writeTimeout)))));
}
}
In spite of the Customizer, the WriteTimeout is Not Working for the API.
Instead of defining a WebServerFactoryCustomizer bean, create a bean of NettyReactiveWebServerFactory to override Spring's auto-configuration.
#Bean
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
NettyReactiveWebServerFactory webServerFactory = new NettyReactiveWebServerFactory();
webServerFactory.addServerCustomizers(new MyCustomizer());
return webServerFactory;
}
Now the MyCustomizer will look something like this:
public class MyCustomizer implements NettyServerCustomizer {
#Override
public HttpServer apply(HttpServer httpServer) {
return httpServer.tcpConfiguration(tcpServer -> tcpServer.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)
.bootstrap(serverBootstrap -> serverBootstrap.childHandler(new ChannelInitializer<Channel>() {
#Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast("writeTimeoutHandler", new WriteTimeoutHandler(1));
}
}))
);
}
}
This is the way suggested in the official API doc

How to trigger a functionality after RabbitMQ retry max attempts are over? (Spring Integration - RabbitMQ Listener)

I want to trigger an email, after RabbitMQ Listener retrials are over and still if the process of handle failed.
retry logic is working with below code. But how to trigger the functionality (email trigger) once the max retrial attempts are over.
#Bean
public SimpleMessageListenerContainer container() {
SimpleMessageListenerContainer container =
new SimpleMessageListenerContainer(connectionFactory());
container.setQueues(myQueue());
container.setDefaultRequeueRejected(false);
Advice[] adviceArray = new Advice[]{interceptor()};
container.setAdviceChain(adviceArray);
return container;
}
#Bean
public IntegrationFlow inboundFlow() {
return IntegrationFlows.from(
Amqp.inboundAdapter(container()))
.log()
.handle(listenerBeanName, listenerMethodName)
.get();
}
#Bean
RetryOperationsInterceptor interceptor() {
return RetryInterceptorBuilder.stateless()
.maxAttempts(retryMaxAttempts)
.backOffOptions(initialInterval, multiplier, maxInterval)
//.recoverer(new RejectAndDontRequeueRecoverer())
.recoverer(new CustomRejectAndRecoverer())
.build();
}
Adding with code of CustomeRecover
#Service
public class CustomRejectAndRecoverer implements MessageRecoverer {
#Autowired
private EmailGateway emailgateway;
#Override
public void recover(Message message, Throwable cause) {
// INSERT CODE HERE.... HOW TO CALL GATEWAY
// emailgateway.sendMail(cause);
throw new ListenerExecutionFailedException("Retry Policy Exhausted",
new AmqpRejectAndDontRequeueException(cause), message);
} }
That's exactly what a .recoverer() in that RetryInterceptorBuilder is for.
You use there now a RejectAndDontRequeueRecoverer, but no body stops you to implement your own MessageRecoverer with the delegation to the RejectAndDontRequeueRecoverer and sending a message to some MessageChannel with a sending emails logic.

in Spring amqp how to encapsulate RabbitListener to a high level

I use spring amqp in my project, and I use implements ChannelAwareMessageListener for re-send and handle exception to make rabbit listener more stable:
public abstract class AbstractMessageListener implements ChannelAwareMessageListener {
#Autowired
private Jackson2JsonMessageConverter messageConverter;
#Autowired
private RedisTemplate<String, Object> redisTemplate;
/** where comsumer really do biz */
public abstract void receiveMessage(Message message, MessageConverter messageConverter);
#Override
public void onMessage(Message message, Channel channel) throws Exception {
MessageProperties messageProperties = message.getMessageProperties();
Long deliveryTag = messageProperties.getDeliveryTag();
Long consumerCount = redisTemplate.opsForHash().increment(MQConstants.MQ_CONSUMER_RETRY_COUNT_KEY,
messageProperties.getMessageId(), 1);
try {
receiveMessage(message, messageConverter);
channel.basicAck(deliveryTag, false);
redisTemplate.opsForHash().delete(MQConstants.MQ_CONSUMER_RETRY_COUNT_KEY,
messageProperties.getMessageId());
} catch (Exception e) {
if (consumerCount >= MQConstants.MAX_CONSUMER_COUNT) {
channel.basicReject(deliveryTag, false);
} else {
Thread.sleep((long) (Math.pow(MQConstants.BASE_NUM, consumerCount)*1000));
channel.basicNack(deliveryTag, false, true);
}
}
}
then we can receive by extend our AbstractMessageListener like that:
public class BizMessageListener extends AbstractMessageListener {
Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void receiveMessage(Message message, MessageConverter messageConverter) {
//do our own biz
}
}
but one day my boss said this way is too Invasion you mush use annotation instead, so I found something like that: Spring RabbitMQ - using manual channel acknowledgement on a service with #RabbitListener configuration
where I can use annotation as
#RabbitListener(queues = "so38728668")
public void receive(String payload, Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) long tag)
throws IOException {
but how can I encapsulate #RabbitListener to a high level to combine my own re-send msg code in my first code sample , for example there is a annotation as RabbitResenderListener
#RabbitResenderListener(queues = "so38728668")
public void receive(Message msg)
throws IOException {
// just do biz
}
this annotation give the method re-send msg and error-handle ability, so that the method only do biz. thks
Not sure what you expect from us, but seems for me annotations are not for extensions and extracting abstractions like we can do with classes.
Anyway I can share some idea you may try.
There is a method level #RabbitHandler annotation. So, you can mark a method in super class with that and do all the infrastructure logic. The target implementation should be marked with the particular #RabbitListener on the class to bring the required queues configuration. The #RabbitHandler method will call the polymorphic method from the inheritor.
Does it sound reasonable?

RabbitMQ request/response "RabbitTemplate is not configured as listener"

I'm testing request/response pattern with Spring-AMQP rabbitmq implementation and I can't make it work...
I've got configured following artifacts:
test_exchange with greeting queue. Routing key = greeting
reply_exchange with replies queue. Routing key = replies
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory =
new CachingConnectionFactory("....IP of broker...");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
return connectionFactory;
}
#Bean
public Queue greeting() {
return new Queue("greeting");
}
#Bean
public Queue replies() {
return new Queue("replies");
}
MessageListener receiver() {
return new MessageListenerAdapter(new RabbitMqReceiver(), "onMessage");
}
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, Queue replies) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setExchange("test_exchange");
template.setRoutingKey("greeting");
template.setReplyAddress("reply_exchange"+"/"+replies.getName());
template.setReplyTimeout(60000);
return template;
}
#Bean
public SimpleMessageListenerContainer replyContainer(ConnectionFactory connectionFactory,
RabbitTemplate rabbitTemplate, Queue replies) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setMessageListener(rabbitTemplate);
container.setQueues(replies);
return container;
}
#Bean
public SimpleMessageListenerContainer serviceListenerContainer(
ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueues(greeting());
container.setMessageListener(receiver());
return container;
}
I was following example at github, but it crashes with:
Caused by: java.lang.IllegalStateException: RabbitTemplate is not configured as
MessageListener - cannot use a 'replyAddress': reply_exchange/replies
Documentation says:
Starting with version 1.5, the RabbitTemplate will detect if it has been configured as a MessageListener to receive replies. If not, attempts to send and receive messages with a reply address will fail with an IllegalStateException (because the replies will never be received).
This is excellent, but how RabbitTemplate does that? How does it detect if it's configured as MessageListener?
thanks in advance
PS: Send code:
public void send() {
Message message = MessageBuilder.withBody("Payload".getBytes())
.setContentType("text/plain")
.build();
Message reply = this.template.sendAndReceive(message);
System.out.println("Reply from server is: "+new String(reply.getBody()));
}
When the reply container starts, it detects that the template is ListenerContainerAware and calls expectedQueueNames() to retrieve the reply queues (or null if the replyAddress has the form exch/rk); if a non-null result is returned, the container checks that the queue is correct; if exch/rk is the reply address, you would get this
logger.debug("Cannot verify reply queue because it has the form 'exchange/routingKey'");
This method unconditionally sets the isListener boolean which avoids that exeption. So it seems like the container hasn't started before you sent your message - are you sending before the context is fully initialized?
Note that since RabbitMQ implemented direct reply-to, it is generally not necessary any more to use a reply container (unless you want HA reply queues or need an explicit reply queue for some other reason). Direct reply-to removed the performance problem that drove us to implement the reply container mechanism.
Gary, your intuition was perfect, as always.
I changed my send code from:
#SpringBootApplication
public class App
{
#Bean(initMethod="send")
public RabbitMqSender sender() {
final RabbitMqSender sender = new RabbitMqSender();
return sender;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(App.class, args);
}
}
to:
#SpringBootApplication
public class App
{
public static void main(String[] args) throws Exception {
final ConfigurableApplicationContext configAppContext = SpringApplication.run(App.class, args);
final RabbitMqSender sender = configAppContext.getBean(RabbitMqSender.class);
sender.send();
}
}
to ensure to be sending when all beans are ready and request/response WORKS EXCELLENT!
Yes, I will definitelly try Direct-TO pattern as well.
Thanks you Gary for help.
regards
Tomas

How to send & consume Object in Spring AMQP?

I want to send and consume the custom object as below using Spring AMQP.
Producer code
Record record = new Record("message1", new Date());
rabbitTemplate.convertAndSend(record);
Can anyone provide spring amqp #configuration settings for sending and consuming messages as above. Thanks!!!
You should take a look at the Sample Applications; some of them use #Configuration.
But, essentially, you need...
#Bean
public SimpleMessageListenerContainer container() {
SimpleMessageListenerContainer container =
new SimpleMessageListenerContainer(connectionFactory());
MessageListenerAdapter adapter = new MessageListenerAdapter(myListener());
container.setMessageListener(adapter);
container.setQueues(foo());
return container;
}
#Bean
public Object myListener() {
return new Foo();
}
and the listener can be a POJO...
public class Foo {
public void handleMessage(Record foo) {
System.out.println(foo);
}
}
EDIT:
I added a Gist here for XML version.