RabbitListenerTestHarness injects actual objects in listeners - java

Scenario:
Junit for a microservice which listens to a queue and posts to an exchange in rabbitMQ after data extraction.
Issue:
RabbitListenerTestHarness is creating mock object for the Rabbit
Listener class alone, Actual objects are being instantiated for
Listeners Autowired components
I couldnt find a way to manually inject mock beans into the listener. This causes Junit to post the test messages to the actual queues configured in the microservice during Junit Execution.
Workaround: The only way I could use the rabbit-test project is to configure test exchange for posting the messages during Junit execution.
Query:
I wanted to understand, if there is any way better way of writing Junit for a Rabbit Listener. Also i wanted to understand if there is a way to maually inject mock objects to the Rabbit Listeners autowired components.
Sample code Snippet:
Rabbit Listener Class
#RabbitListener(id = "id", bindings = #QueueBinding(value = #Queue(value = "sampleQueue", durable = "true", autoDelete = "false"),key = "sampleRoutingKey", exchange = #Exchange(value = "sampleExchange", durable = "true", ignoreDeclarationExceptions = "true", type = EXCHANGE_TYPE)))
public void getMessageFromQueue(#Payload EventModel event) throws ListenerExecutionFailedException, JAXBException {
dataExporterService.exportDataAndPostToRabbit(event);
}
Service class
#Autowired
DataExtractorRepository dataExtractorRepository;
#Autowired
DataPublihserRepository dataPublisherRepo;
public void exportDataAndPostToRabbit(EventModel event) throws JAXBException {
dataPublisherRepo.sendMessageToExchange(dataExtractorRepository.extractOrderData(event), exchangeName, routingKeyValue);
}
DataPublihserRepository has rabbitTemplate internally Autowired. DataExtractorRepository connects to DB internally for retriving the message.
Test class
#Autowired
private RabbitListenerTestHarness harness;
#Autowired
private RabbitTemplate rabbitTemplate;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
DataExporterController = this.harness.getSpy("id");
}
#Test
public void shouldReceiveMessage() throws Exception {
LatchCountDownAndCallRealMethodAnswer answer = new LatchCountDownAndCallRealMethodAnswer(1);
doAnswer(answer).when(DataExporterController).getMessageFromQueue(any(EventModel.class));
rabbitTemplate.convertAndSend("sampleExchange", "sampleRoutingKey", createMessage());
assertTrue(answer.getLatch().await(10, TimeUnit.SECONDS));
verify(DataExporterController, times(1)).getMessageFromQueue(any(OrderEventsModel.class));
verify(orderDataExporterController, times(1)).getMessageFromQueue(any(OrderEventsModel.class));
}
private Message createMessage() {
String inputObject = "{\"id\":12345}";
MessageProperties props = MessagePropertiesBuilder.newInstance().setContentType(MessageProperties.CONTENT_TYPE_JSON).build();
return new Message(inputObject.getBytes(), props);
}

The harness is intended as a mechanism to verify that the listener received the data in an integration test. To unit test a listener, invoke its onMessage Method.
For example, using Mockito, given
public class MyListener {
#Autowired
private SomeService service;
#RabbitListener(id = "myListener", queues = "foo")
public void listen(Foo foo) {
this.service.process(foo);
}
}
and
public interface SomeService {
void process(Foo foo);
}
then
#RunWith(SpringRunner.class)
public class So53136882ApplicationTests {
#Autowired
private RabbitListenerEndpointRegistry registry;
#Autowired
private SomeService service;
#Test
public void test() throws Exception {
SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) this.registry
.getListenerContainer("myListener");
ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener();
Message message = MessageBuilder.withBody("{\"bar\":\"baz\"}".getBytes())
.andProperties(MessagePropertiesBuilder.newInstance()
.setContentType("application/json")
.build())
.build();
listener.onMessage(message, mock(Channel.class));
verify(this.service).process(new Foo("baz"));
}
#Configuration
#EnableRabbit
public static class config {
#Bean
public ConnectionFactory mockCf() {
return mock(ConnectionFactory.class);
}
#Bean
public MessageConverter converter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(mockCf());
factory.setMessageConverter(converter());
factory.setAutoStartup(false);
return factory;
}
#Bean
public MyListener myListener() {
return new MyListener();
}
#Bean
public SomeService service() {
return mock(SomeService.class);
}
}
}
Notice that the container factory does not start the listener container.
For testing publishing, inject a mock RabbitOperations which is implemented by RabbitTemplate.
For example, given
public class SomeServiceImpl implements SomeService {
#Autowired
private RabbitOperations rabbitOperations;
#Override
public void process(Foo foo) {
this.rabbitOperations.convertAndSend(
"someExchange", "someRoutingKey", new Foo(foo.getBar().toUpperCase()));
}
}
and
#Bean
public SomeService service() {
return new SomeServiceImpl();
}
#Bean
public RabbitOperations rabbitTemplate() {
return mock(RabbitOperations.class);
}
then
#Test
public void test() throws Exception {
SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) this.registry
.getListenerContainer("myListener");
ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener();
Message message = MessageBuilder.withBody("{\"bar\":\"baz\"}".getBytes())
.andProperties(MessagePropertiesBuilder.newInstance()
.setContentType("application/json")
.build())
.build();
listener.onMessage(message, mock(Channel.class));
verify(this.rabbitTemplate).convertAndSend("someExchange", "someRoutingKey", new Foo("BAZ"));
}

Related

Override bean in test with non-mocked bean

I want to unit test a class that uses a named spring bean of a List, but I can't figure out how to override the bean with my own test bean filled with mocks.
The class that defines the named bean:
#Configuration
#RequiredArgsConstructor
public class JmsReceiveConfig {
#Bean(name = "FooConsumers")
List<JmsConsumer> messageConsumers(QueuedReceiveService fooMessageListener) throws Exception {
List<JmsConsumer> list = new ArrayList<>();
for (MyJmsProperties.Broker broker : myJmsProperties.getBrokers()) {
JmsConsumer jmsConsumer =
messageConsumer(broker, myJmsProperties.getKeystore(), myJmsProperties.getKeystorePassword(),
myJmsProperties.getTruststore(), myJmsProperties.getTruststorePassword(), fooMessageListener);
list.add(jmsConsumer);
}
return list;
}
...
}
My class that uses the named bean:
#Component
#ConditionalOnEnabledHealthIndicator("barIndicator")
#RequiredArgsConstructor
public class MyHealthIndicator implements HealthIndicator {
#Inject
#Qualifier("FooConsumers")
private List<JmsConsumer> jmsConsumersList;
#Override
public Health health() {
int connectedCount = 0;
Map<String, Object> detailsMap = new HashMap<>();
for (JmsConsumer consumer : jmsConsumersList) {
if (consumer.isConnected()) {
connectedCount++;
}
detailsMap.put(consumer.getAlias(), consumer.isConnected());
}
return Health.up()
.withDetails(detailsMap)
.build();
}
}
I tried this test class:
#ExtendWith(SpringExtension.class)
public class MyHealthIndicatorTest {
private MyHealthIndicator myHealthIndicator;
#Mock
JmsConsumer jmsConsumer1;
#Mock
JmsConsumer jmsConsumer2;
#Bean(name = "FooConsumers")
List<JmsConsumer> messageConsumers(QueuedReceiveService fooMessageListener) throws Exception {
ArrayList<JmsConsumer> jmsConsumers = new ArrayList<>();
jmsConsumers.add(jmsConsumer1);
jmsConsumers.add(jmsConsumer2);
return jmsConsumers;
}
#BeforeEach
public void setup() throws Exception {
myHealthIndicator = new MyHealthIndicator();
}
#Test
public void testStatusUpAll() {
Mockito.when(jmsConsumer1.getAlias())
.thenReturn("jmsConsumer1");
Mockito.when(jmsConsumer1.isConnected())
.thenReturn(true);
Mockito.when(jmsConsumer2.getAlias())
.thenReturn("jmsConsumer2");
Mockito.when(jmsConsumer2.isConnected())
.thenReturn(true);
Health healthCheck = myHealthIndicator.health();
assertEquals(healthCheck.getStatus(), Status.UP);
}
}
I expected the named bean I defined in the test class to be used in the #Test method inside the myHealthIndicator.health() call, but instead when that call runs, the jmsConsumers list within it is null, giving a NullPointerException on the for (JmsConsumer consumer : jmsConsumersList) line.
I also tried using a mock JmsReceiveConfig so I could change my #BeforeEach to:
List<JmsConsumer> jmsConsumers = new ArrayList<>();
jmsConsumers.add(jmsConsumer1);
jmsConsumers.add(jmsConsumer2);
Mockito.when(jmsReceiveConfig.messageConsumers(Mockito.any(QueuedReceiveService.class)))
.thenReturn(jmsConsumers);
radItsHealthIndicator = new RadItsHealthIndicator();
but I still get the same NPE exception in the same spot.
Don't bother using SpringExtension, just use MockitoExtension.
Give MyHealthIndicator a constructor which sets jmsConsumersList.
So setup becomes:
#BeforeEach
public void setup() throws Exception {
ArrayList<JmsConsumer> jmsConsumers = new ArrayList<>();
jmsConsumers.add(jmsConsumer1);
jmsConsumers.add(jmsConsumer2);
myHealthIndicator = new MyHealthIndicator(jmsConsumers);
}
The rest of your test can stay the same (of course you don't need messageConsumers().)

Spring Boot + RabbitMQ: how to convert received object using convertSendAndReceive() method?

How can I deserialize a message using convertSendAndReceive() method? It gives me NullPointerException due to not being able to find the required class for deserialization in another package. Packages are marked in the code.
Listener receives and sends messages normally
package org.dneversky.user;
#EnableRabbit
#Component
public class TestListener {
private static final Logger logger = LoggerFactory.getLogger(TestListener.class);
#Autowired
private RabbitTemplate rabbitTemplate;
#RabbitListener(queues = RabbitMQConfig.RECEIVE_QUEUE)
public void doGet(UserReplyMessage message) {
logger.info("Received message: {}", message);
UserReplyMessage response = new UserReplyMessage();
logger.info("Sending message: {}", response);
rabbitTemplate.convertSendAndReceive(RabbitMQConfig.RPC_EXCHANGE,
RabbitMQConfig.REPLY_QUEUE, response);
}
}
Configuration of the listener
package org.dneversky.user.config;
#Configuration
public class RabbitMQConfig {
public static final String RECEIVE_QUEUE = "rpc_queue";
public static final String REPLY_QUEUE = "reply_queue";
public static final String RPC_EXCHANGE = "rpc_exchange";
#Bean
public TopicExchange rpcExchange() {
return new TopicExchange(RPC_EXCHANGE);
}
#Bean
public Queue receiveQueue() {
return new Queue(RECEIVE_QUEUE);
}
#Bean
public Queue replyQueue() {
return new Queue(REPLY_QUEUE);
}
#Bean
public Binding receiveBinding() {
return BindingBuilder.bind(receiveQueue()).to(rpcExchange()).with(RECEIVE_QUEUE);
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
Sender sends a message normally, but it can't to deserialize returning message
package org.dneversky.gateway.servie.impl;
#Service
public class UserServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
#Autowired
private RabbitTemplate rabbitTemplate;
public UserPrincipal getUserByUsername(String username) {
UserResponse message = new UserResponse(username);
logger.info("Sending created message: {}", message);
UserResponse result = (UserResponse) rabbitTemplate.convertSendAndReceive(RabbitMQConfig.RPC_EXCHANGE, RabbitMQConfig.RPC_QUEUE, message);
logger.info("Getting response: {}", result);
return null;
}
}
Configuration of the Sender
package org.dneversky.gateway.config;
#Configuration
public class RabbitMQConfig {
public static final String RPC_QUEUE = "rpc_queue";
public static final String REPLY_QUEUE = "reply_queue";
public static final String RPC_EXCHANGE = "rpc_exchange";
#Bean
public Queue rpcQueue() {
return new Queue(RPC_QUEUE);
}
#Bean
public Queue replyQueue() {
return new Queue(REPLY_QUEUE);
}
#Bean
public TopicExchange rpcExchange() {
return new TopicExchange(RPC_EXCHANGE);
}
#Bean
public Binding binding() {
return BindingBuilder.bind(replyQueue()).to(rpcExchange()).with(REPLY_QUEUE);
}
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setExchange(RPC_EXCHANGE);
rabbitTemplate.setReplyAddress(REPLY_QUEUE);
rabbitTemplate.setReplyTimeout(6000);
rabbitTemplate.setMessageConverter(messageConverter());
return rabbitTemplate;
}
#Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public SimpleMessageListenerContainer replyContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(REPLY_QUEUE);
container.setMessageListener(rabbitTemplate(connectionFactory));
return container;
}
}
Error log
2022-05-22 17:12:31.344 ERROR 16920 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name. Class not found [org.dneversky.user.model.UserReplyMessage]] with root cause
java.lang.ClassNotFoundException: org.dneversky.user.model.UserReplyMessage
by default, the producer set the _TypeID_ header as the class name used for the serialization of the object
then consumer uses _TypeID_ header to know the class that should use to convert the JSON to java instance
you use two different classes to serialize and deserialize the object and you have to configure the converter
inside your replyContainer I couldn't see your messageConverter bean. In default it uses java objects to send and receive messages without converting them into human readable json.
#Bean
public SimpleRabbitListenerContainerFactory customListenerContainerFactory(ConnectionFactory connectionFactory,
MessageConverter jsonMessageConverter) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jsonMessageConverter);
return factory;
}
for your consumer;
#RabbitListener(queues = RabbitConstants.YOUR_QUEUE_NAME, containerFactory = "customListenerContainerFactory")
public void onMessage(#Valid YourEvent YourEvent){
//your code
}
Inside the Listener class, you need to add this line to bind your message converter
#Bean
public SimpleRabbitListenerContainerFactory jsaFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setMessageConverter(jsonMessageConverter());
return factory;
}
Also, in the TestListener class, you should replace this line
#RabbitListener(queues = RabbitMQConfig.RECEIVE_QUEUE)
with this one
#RabbitListener(queues = RabbitMQConfig.RECEIVE_QUEUE,containerFactory="jsaFactory")

org.springframework.amqp.AmqpException: No method found for class java.util.LinkedHashMap

I have a problem with Spring Boot Rabbit Mq. I have 2 listeners which listen the same queue but handle different objects:
#Service
#RabbitListener(queues = "#{changeDataQueue.name}")
public class CreateDayAheadTradeListener implements DayAheadTradeEventListener<CreateDayAheadTradeRequest> {
#Autowired
private DayAheadTradeRepository repository;
#Override
#RabbitHandler
public void process(final CreateDayAheadTradeRequest event) {
// Do something
}
}
And the next one:
#Service
#RabbitListener(queues = "#{changeDataQueue.name}")
public class UpdateDayAheadTradeListener implements DayAheadTradeEventListener<ModifyDayAheadTradeStatusRequest> {
#Autowired
private DayAheadTradeRepository repository;
#Override
#RabbitHandler
public void process(final ModifyDayAheadTradeStatusRequest event) {
// Do something
}
}
The Rabbit Config is:
#EnableRabbit
#Configuration
public class RabbitMqConfig {
private final String createDayAheadTradesRouting = CreateDayAheadTradeRequest.class
.getAnnotation(Routing.class)
.routingKey();
private final String updateDayAheadTradesRouting = ModifyDayAheadTradeStatusRequest.class
.getAnnotation(Routing.class)
.routingKey();
private final String requestDayAheadTradesRouting = DayAheadTradeRequest.class
.getAnnotation(Routing.class)
.routingKey();
#Bean
public TopicExchange topicExchange() {
return new TopicExchange("schedule"); // exchange name
}
#Bean
public Queue changeDataQueue() {
return QueueBuilder
.durable("dayahead.trades.data") // queue template name
.build();
}
#Bean
public Queue requestQueue() {
return QueueBuilder
.nonDurable("dayahead.trades.request") // queue template name
.exclusive()
.build();
}
#Bean
public Binding createDataBinding(final Queue changeDataQueue, final TopicExchange topicExchange) {
return BindingBuilder
.bind(changeDataQueue)
.to(topicExchange)
.with(createDayAheadTradesRouting);
}
#Bean
public Binding updateDataBinding(final Queue changeDataQueue, final TopicExchange topicExchange) {
return BindingBuilder
.bind(changeDataQueue)
.to(topicExchange)
.with(updateDayAheadTradesRouting);
}
#Bean
public Binding requestBinding(final Queue requestQueue, final TopicExchange topicExchange) {
return BindingBuilder
.bind(requestQueue)
.to(topicExchange)
.with(requestDayAheadTradesRouting);
}
#Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
final ObjectMapper mapper = EventUtils.createMapper();
mapper.registerModule(new MarketdataModule());
return new Jackson2JsonMessageConverter(mapper);
}
}
The problem is that if I create 2 separate queues for different types of objects - everything works fine, but the idea is to use one queue for several types of objects. But I have the following exception:
org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method 'no match' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:219)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandlerAndProcessResult(MessagingMessageListenerAdapter.java:143)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:132)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1569)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1488)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1476)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1467)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1411)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:958)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:908)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:81)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1279)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1185)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.springframework.amqp.AmqpException: No method found for class java.util.LinkedHashMap
at org.springframework.amqp.rabbit.listener.adapter.DelegatingInvocableHandler.getHandlerForPayload(DelegatingInvocableHandler.java:149)
at org.springframework.amqp.rabbit.listener.adapter.DelegatingInvocableHandler.invoke(DelegatingInvocableHandler.java:129)
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:61)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:211)
... 13 more
I can't find any information about it, I hope someone knows about it. Thanks

Java Spring send back message to queue from consumer

I have a service that sends message to rabbitmq and the consumer do some manipulation of the message and re-queue them.
I can successfully send to rabbitmq the initial message but the problem is i cannot resend to rabbitmq any consumed message if the message requires modifications.
#Service
public class MyService {
/**
* The template
*/
#Autowired
private AmqpTemplate amqpTemplate;
private final RabbitMQConfig config;
public void send(String message) {
try {
amqpTemplate.convertAndSend("ex", "r", message);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Then in my config i have setup:
#Bean
public ConnectionFactory connectionFactory() { /* working code */ }
#Bean
public Queue myQueue() { return new Queue("my-queue");
// etc...
#Bean
MessageListenerAdapter myListenerAdapter(MyListener listener) {
return new MessageListenerAdapter(listener, "listener");
}
#Bean
MyListener myListener() {
return new MyListener();
}
then...
public class MyListener {
public void receiveMessage(String message) {
// ... some code
// if message requires modification, then repush
new Repush().push(message);
}
}
I tried to create a new class with new but the "myService" always null
#Component
public class Repush {
#Autowired
private MyService myService;
public void push(String message) {
// myService is null at this point
}
}
Don't use new for bean creation. Spring injects fields only in beans. Your MyListener is a bean. Just add Repush field with #Autowired annotation in this class.
public class MyListener {
#Autowired
private Repush repush;
public void receiveMessage(String message) {
// ... some code
// if message requires modification, then repush
repush.push(message);
}
}
If you declare myService as a bean in the application context as well as Repush as a bean you can then inject it into MyListener using #Autowired.
By creating Repush using new at point-in-time within the listener method, you are not getting a bean that is cognizant of the context you are in.

Spring - Validate incoming message in RabbitMQ listener

I am using Spring Boot framework. I want to send an object from a service to another service via RabbitMQ like this:
Service A:
rabbitTemplate.convertAndSend("queue", createAccountRequestMessage);
Service B:
#RabbitListener(queues = "queue")
public void onAccountRequested(#Valid CreateAccountRequestMessage createAccountRequestMessage, Channel channel, #Header(AmqpHeaders.DELIVERY_TAG, long tag) throws IOException
{
}
In CreateAccountRequestMessage class I have defined some validation annotations like #NotEmpty, #NotNull and etc, but when I'm sending wrong message from service A to service B, #Valid annotation doesn't work and CreateAccountRequestMessage object is not validated before invoke onAccountRequested method.
You need to set the validator in DefaultMessageHandlerMethodFactory.
#Autowired
SmartValidator validator;
#Bean
public DefaultMessageHandlerMethodFactory messageHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(this.validator);
return factory;
}
Then you also need to specify the #Payload annotation along with the #Valid annotation.
#RabbitListener(queues = "queue")
public void onAccountRequested(#Valid #Payload CreateAccountRequestMessage
createAccountRequestMessage, Channel channel,
#Header(AmqpHeaders.DELIVERY_TAG, long tag) throws IOException
{
}
Now MethodArgumentNotValidException will be thrown and the message will be discarded, or you can send the message to a dead letter exchange.
I had the same problem. The answer of #Praveer works well except SmartValidator. I post here my solution, which is inspired by this article https://blog.trifork.com/2016/02/29/spring-amqp-payload-validation/
#Configuration
#EnableRabbit
#Slf4j
public class CmsMQConfig implements RabbitListenerConfigurer {
#Value("${dw.rabbitmq.hosts}")
private String hosts;
#Value("${dw.rabbitmq.username}")
private String username;
#Value("${dw.rabbitmq.password}")
private String password;
#Value("${dw.rabbitmq.virtual-host}")
private String virtualHost;
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setMessageConverter(messageConverter());
return factory;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses(hosts);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(virtualHost);
return connectionFactory;
}
#Bean
public Jackson2JsonMessageConverter messageConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return new Jackson2JsonMessageConverter(mapper);
}
#Bean
public DefaultMessageHandlerMethodFactory defaultHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(amqpValidator());
return factory;
}
#Bean
public Validator amqpValidator() {
return new OptionalValidatorFactoryBean();
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setContainerFactory(rabbitListenerContainerFactory());
registrar.setMessageHandlerMethodFactory(defaultHandlerMethodFactory());
}
}

Categories