I am trying to run an integration test for my RabbitListener in Spring AMQP while the broker is not running so I am using TestRabbitTemplate. I am using a Jacksonized object and the Jackson2JsonMessageConverter was set for the TestRabbitTemplate. However upon sending a message to the exchange, I am getting the following exception.
org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method could not be invoked with the incoming message
Caused by: org.springframework.messaging.converter.MessageConversionException: Cannot convert from [[B] to [com.murex.em.demo.springamqpdemo.message.CustomMessage] for GenericMessage [payload=byte[27], headers={amqp_contentEncoding=UTF-8, amqp_contentLength=27, amqp_replyTo=testRabbitTemplateReplyTo, id=fb0352d2-2abb-ae06-9b2e-03da1fc19e43, amqp_lastInBatch=false, contentType=application/json, __TypeId__=com.murex.em.demo.springamqpdemo.message.CustomMessage, timestamp=1676625212607}], failedMessage=GenericMessage [payload=byte[27], headers={amqp_contentEncoding=UTF-8, amqp_contentLength=27, amqp_replyTo=testRabbitTemplateReplyTo, id=fb0352d2-2abb-ae06-9b2e-03da1fc19e43, amqp_lastInBatch=false, contentType=application/json, __TypeId__=com.murex.em.demo.springamqpdemo.message.CustomMessage, timestamp=1676625212607}]
at org.springframework.messaging.handler.annotation.support.PayloadMethodArgumentResolver.resolveArgument(PayloadMethodArgumentResolver.java:145)
at org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor$OptionalEmptyAwarePayloadArgumentResolver.resolveArgument(RabbitListenerAnnotationBeanPostProcessor.java:1053)
at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:118)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:147)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:115)
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:77)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:263)
... 78 more
Here are the relevant chunks of my code:
#Jacksonized
#Builder
#Value
public class CustomMessage {
String id;
String name;
}
#Component
public class InwardMessageConsumer {
#RabbitListener(bindings = #QueueBinding(
value = #Queue(name = "inward.messageQueue", durable = "true"),
exchange = #Exchange(value = "inward", type = "topic"),
key = "inwardRoutingKey")
)
public String processMessage(CustomMessage customMessage) {
return customMessage.getName();
}
}
#SpringJUnitConfig
#SpringBootTest
class BrokerNotRunningIT {
#Autowired
private TestRabbitTemplate template;
#Test
public void testSendAndReceive() {
CustomMessage customMessage = CustomMessage.builder()
.id("123")
.name("name1")
.build();
assertThat(template.convertSendAndReceive("inward", "inward.messageQueue", customMessage)).isEqualTo("name1");
}
#TestConfiguration
public static class RabbitTestConfiguration {
#Bean
public TestRabbitTemplate testRabbitTemplate(
ConnectionFactory mockConnectionFactory,
Jackson2JsonMessageConverter jackson2JsonMessageConverter
) {
TestRabbitTemplate testRabbitTemplate = new TestRabbitTemplate(mockConnectionFactory);
testRabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
return testRabbitTemplate;
}
#Bean
public ConnectionFactory mockConnectionFactory() throws IOException {
ConnectionFactory factory = mock(ConnectionFactory.class);
Connection connection = mock(Connection.class);
Channel channel = mock(Channel.class);
AMQP.Queue.DeclareOk declareOk = mock(AMQP.Queue.DeclareOk.class);
willReturn(connection).given(factory).createConnection();
willReturn(channel).given(connection).createChannel(anyBoolean());
given(channel.isOpen()).willReturn(true);
given(channel.queueDeclare(anyString(), anyBoolean(), anyBoolean(), anyBoolean(), anyMap()))
.willReturn(declareOk);
return factory;
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
return factory;
}
#Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory) {
return new SimpleMessageListenerContainer(connectionFactory);
}
}
}
Apparently I needed to set the message converter in the RabbitListenerContainerFactory as well like this:
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory, Jackson2JsonMessageConverter jackson2JsonMessageConverter) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jackson2JsonMessageConverter);
return factory;
}
Related
I have 2 RabbitMQ connections and I want to use #RabbitListener on one of the queues in one of the RabbitMQ connection, but with my below code, the #RabbitListener is not able to capture message, even there is message in that particular queue. Can someone help me?
RabbitMQ 1 Connection configuration
#Data
#Configuration
#ConfigurationProperties(prefix="p.spring.rabbitmq")
public class RabbitMQPConfig{
private String addresses;
private String username;
private String password;
private int port;
private String virtualHost;
#Bean(name="pRabbitConnectionFactory")
#Primary
public ConnectionFactory rabbitConnectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses(addresses);
connectionFactory.setPort(port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password)
connectionFactory,setVirtualHost(virtualHost);
return connectionFactory;
}
#Bean(name="pRabbitMessageConverter")
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
#Bean(name="pRabbitTemplate")
public RabbitTemplate rabbitTemplate(#Qualifier("pRabbitConnectionFactory") ConnectionFactory connectionFactory){
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(jsonMessageConverter());
return rabbitTemplate;
}
}
RabbitMQ 2 Connection configuration
#Data
#Configuration
#ConfigurationProperties(prefix = "d.spring.rabbitmq")
public class RabbitMQDConfig{
private String addresses;
private String username;
private String password;
private int port;
private String virtualHost;
#Bean(name = "dRabbitConnectionFactory")
public ConnectionFactory rabbitConnectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses(addresses);
connectionFactory.setPort(port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password)
connectionFactory,setVirtualHost(virtualHost);
return connectionFactory;
}
#Bean(name="dRabbitMessageConverter")
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
#Bean(name="dRabbitTemplate")
public RabbitTemplate rabbitTemplate(#Qualifier("dRabbitConnectionFactory") ConnectionFactory connectionFactory){
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(jsonMessageConverter());
return rabbitTemplate;
}
#Bean(name="dRabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer,
#Qualifier("dRabbitConnectionFactory") ConnectionFactory connectionFactory
){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
}
RabbitMQ Listener
#Configuration
public class RabbitMQListen{
#RabbitListener(queues="<put the queue name here>", containerFactory="dRabbitListenerContainerFactory")
public void receive(String task){ //Did not receive any message even there is messsage in the queue..
System.out.println("");
}
}
I am using Spring Rabbit 2.3.6 for my spring application. I have list of Comment entity and send to message queue via this function
public void deletePost(long postId){
Post post = postRepository.findById(postId)
.orElseThrow(() -> new PostNotFoundException(String.valueOf(postId)));
List<Comment> comments = postBase.getComments(post);
postRepository.delete(post);
//post event
Map<String, Object> inputs= new HashMap<>();
inputs.put(InputParam.OBJECT_ID, postId);
inputs.put(InputParam.COMMENTS, comments);
messagingUtil.postEvent(SnwObjectType.POST, SnwActionType.DELETE, inputs);
}
Listening message
#RabbitListener(queues = MessagingConfig.QUEUE)
public void consumeMessageFromQueue(Map<String, Object> inputs) {
}
And this is data I received after listening message. Data type of each element in comment list have been changed, and I can't use it as Comment.
Have you any solution to data type from not being changed?
Updated: Messaging config
#Configuration
public class MessagingConfig {
public static final String QUEUE = "javatechie_queue";
public static final String EXCHANGE = "javatechie_exchange";
public static final String ROUTING_KEY = "javatechie_routingKey";
#Bean
public Queue queue() {
return new Queue(QUEUE);
}
#Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE);
}
#Bean
public Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
#Bean
public MessageConverter converter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public AmqpTemplate template(ConnectionFactory connectionFactory) {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(converter());
return rabbitTemplate;
}
}
My requirement is to listen to two queues and process the messages
I have created two containers
#Bean("listenerForIncomingMessages")
public SimpleRabbitListenerContainerFactory listenerForIncomingMessages() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setConcurrentConsumers(2);
factory.setMaxConcurrentConsumers(2);
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
factory.setPrefetchCount(1);
return factory;
}
#Bean("listenerForMissedDeliveries")
public SimpleRabbitListenerContainerFactory listenerForMissedDeliveries() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setConcurrentConsumers(2);
factory.setMaxConcurrentConsumers(2);
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
factory.setPrefetchCount(1);
return factory;
}
I have two #Configuration classes to process the messages
#Configuration
public class ClassOne {
#RabbitListener(containerFactory = "listenerForIncomingMessages", queues = "IncomingMessage")
public void getAmqpMessage(Message inMessage, Channel channel) throws Exception {
}
}
#Configuration
public class ClassTwo {
#RabbitListener(containerFactory = "listenerForMissedDeliveries", queues = "MissedDelivery")
public void getAmqpMessage(Message inMessage, Channel channel) throws Exception {
}
}
The ClassTwo's RabbitListener works only once during server start. After that it is not working -- The messages are arriving at the queue but they are
not consumed.
What am i doing wrong?
Any pointers to get this working.
Thanks,
I've seen some related questions here, but none worked for me, the rabbit will not serialize my message coming from another application.
Caused by: org.springframework.amqp.AmqpException: No method found for class [B
Below my configuration class to receive the messages.
#Configuration
public class RabbitConfiguration implements RabbitListenerConfigurer{
public final static String EXCHANGE_NAME = "wallet-accounts";
public final static String QUEUE_PAYMENT = "wallet-accounts.payment";
public final static String QUEUE_RECHARGE = "wallet-accounts.recharge";
#Bean
public List<Declarable> ds() {
return queues(QUEUE_PAYMENT, QUEUE_RECHARGE);
}
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
private List<Declarable> queues(String ... names){
List<Declarable> result = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
result.add(makeQueue(names[i]));
result.add(makeBinding(names[i]));
}
return result;
}
private static Binding makeBinding(String queueName){
return new Binding(queueName, DestinationType.QUEUE, EXCHANGE_NAME, queueName, null);
}
private static Queue makeQueue(String name){
return new Queue(name);
}
#Bean
public MappingJackson2MessageConverter jackson2Converter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
return converter;
}
#Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(jackson2Converter());
return factory;
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
}
}
Using this other configuration, the error is almost the same:
Caused by: org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name. Class not found [br.com.beblue.wallet.payment.application.accounts.PaymentEntryCommand]
Configuration:
#Configuration
public class RabbitConfiguration {
public final static String EXCHANGE_NAME = "wallet-accounts";
public final static String QUEUE_PAYMENT = "wallet-accounts.payment";
public final static String QUEUE_RECHARGE = "wallet-accounts.recharge";
#Bean
public List<Declarable> ds() {
return queues(QUEUE_PAYMENT, QUEUE_RECHARGE);
}
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
private List<Declarable> queues(String ... names){
List<Declarable> result = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
result.add(makeQueue(names[i]));
result.add(makeBinding(names[i]));
}
return result;
}
private static Binding makeBinding(String queueName){
return new Binding(queueName, DestinationType.QUEUE, EXCHANGE_NAME, queueName, null);
}
private static Queue makeQueue(String name){
return new Queue(name);
}
}
Can anyone tell me what's wrong with these settings, or what's missing?
No method found for class [B
Means there is a default SimpleMessageConverter which can't convert your incoming application/json. It is just not aware of that content-type and just falls back to the byte[] to return.
Class not found [br.com.beblue.wallet.payment.application.accounts.PaymentEntryCommand]
Means that Jackson2JsonMessageConverter can't convert your application/json because the incoming __TypeId__ header, representing class of the content, cannot be found in the local classpath.
Well, definitely your configuration for the DefaultMessageHandlerMethodFactory does not make sense for the AMQP conversion. You should consider to use SimpleRabbitListenerContainerFactory bean definition and its setMessageConverter. And yes, consider to inject the proper org.springframework.amqp.support.converter.MessageConverter implementation.
https://docs.spring.io/spring-amqp/docs/1.7.3.RELEASE/reference/html/_reference.html#async-annotation-conversion
From the Spring Boot perspective there is SimpleRabbitListenerContainerFactoryConfigurer to configure on the matter:
https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#boot-features-using-amqp-receiving
I'm trying to configure a master/slave replication on Cloudfoundry but I always fall with a 'ERR unknown command 'SLAVEOF''. I'm using Jedis as the underlying client of spring-data-reids with this code:
#Configuration
#ComponentScan(basePackages = { Constants.REDIS_PACKAGE })
#Order(1)
public class KeyValueConfig {
#Inject
#Named("redisMasterConnectionFactory")
private RedisConnectionFactory redisMasterConnectionFactory;
#Inject
#Named("redisSlaveConnectionFactory")
private RedisConnectionFactory redisSlaveConnectionFactory;
#Inject
#Named("redisCacheConnectionFactory")
private RedisConnectionFactory redisCacheConnectionFactory;
#Bean
public StringRedisTemplate redisMasterTemplate() {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(
redisMasterConnectionFactory);
HealthChecks.register(new RedisHealthCheck(stringRedisTemplate,
"master"));
return stringRedisTemplate;
}
#Bean
public StringRedisTemplate redisSlaveTemplate() {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(
redisSlaveConnectionFactory);
HealthChecks
.register(new RedisHealthCheck(stringRedisTemplate, "slave"));
return stringRedisTemplate;
}
#Bean
public StringRedisTemplate redisCacheTemplate() {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(
redisCacheConnectionFactory);
HealthChecks
.register(new RedisHealthCheck(stringRedisTemplate, "cache"));
return stringRedisTemplate;
}
/**
* Properties to support the local and test mode of operation.
*/
#Configuration
#Profile({ Profiles.LOCAL, Profiles.PROD, Profiles.TEST })
static class Default implements MasterSlaveConfig {
#Inject
private Environment environment;
#Bean
public RedisConnectionFactory redisMasterConnectionFactory() {
JedisConnectionFactory redis = new JedisConnectionFactory();
redis.setHostName(environment.getProperty("redis.master.hostname"));
redis.setPort(environment.getProperty("redis.master.port",
Integer.class));
redis.setPassword(environment.getProperty("redis.master.password"));
redis.setUsePool(true);
return redis;
}
#Bean
public RedisConnectionFactory redisSlaveConnectionFactory() {
JedisConnectionFactory redis = new JedisConnectionFactory();
redis.setHostName(environment.getProperty("redis.slave.hostname"));
redis.setPort(environment.getProperty("redis.slave.port",
Integer.class));
redis.setPassword(environment.getProperty("redis.slave.password"));
redis.setUsePool(true);
return redis;
}
#Bean
public RedisConnectionFactory redisCacheConnectionFactory()
throws Exception {
JedisConnectionFactory redis = new JedisConnectionFactory();
redis.setHostName(environment.getProperty("redis.cache.hostname"));
redis.setPort(environment.getProperty("redis.cache.port",
Integer.class));
redis.setPassword(environment.getProperty("redis.cache.password"));
redis.setUsePool(true);
return redis;
}
}
/**
* Properties to support the cloud mode of operation.
*/
#Configuration
#Profile(Profiles.CLOUDFOUNDRY)
static class Cloud implements MasterSlaveConfig {
#Bean
public RedisConnectionFactory redisMasterConnectionFactory()
throws Exception {
CloudPoolConfiguration cloudPoolConfiguration = new CloudPoolConfiguration();
cloudPoolConfiguration.setPoolSize("3-5");
CloudRedisConnectionFactoryBean factory = new CloudRedisConnectionFactoryBean();
factory.setCloudPoolConfiguration(cloudPoolConfiguration);
factory.setServiceName("redis-master");
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
public RedisConnectionFactory redisSlaveConnectionFactory()
throws Exception {
CloudPoolConfiguration cloudPoolConfiguration = new CloudPoolConfiguration();
cloudPoolConfiguration.setPoolSize("3-5");
CloudRedisConnectionFactoryBean factory = new CloudRedisConnectionFactoryBean();
factory.setCloudPoolConfiguration(cloudPoolConfiguration);
factory.setServiceName("redis-slave");
factory.afterPropertiesSet();
RedisConnectionFactory redisSlaveConnectionFactory = initSlaveOnMaster(factory);
return redisSlaveConnectionFactory;
}
private RedisConnectionFactory initSlaveOnMaster(
CloudRedisConnectionFactoryBean factory) throws Exception {
RedisConnectionFactory redisSlaveConnectionFactory = factory
.getObject();
RedisServiceInfo serviceInfo = new CloudEnvironment()
.getServiceInfo("redis-master", RedisServiceInfo.class);
Jedis jedis = (Jedis) redisSlaveConnectionFactory.getConnection()
.getNativeConnection();
jedis.slaveof(serviceInfo.getHost(), serviceInfo.getPort());
return redisSlaveConnectionFactory;
}
#Bean
public RedisConnectionFactory redisCacheConnectionFactory()
throws Exception {
CloudPoolConfiguration cloudPoolConfiguration = new CloudPoolConfiguration();
cloudPoolConfiguration.setPoolSize("3-5");
CloudRedisConnectionFactoryBean factory = new CloudRedisConnectionFactoryBean();
factory.setCloudPoolConfiguration(cloudPoolConfiguration);
factory.setServiceName("redis-cache");
factory.afterPropertiesSet();
return factory.getObject();
}
}
interface MasterSlaveConfig {
RedisConnectionFactory redisMasterConnectionFactory() throws Exception;
RedisConnectionFactory redisSlaveConnectionFactory() throws Exception;
RedisConnectionFactory redisCacheConnectionFactory() throws Exception;
}
}
Ok... After checking the code, I found this:
rename-command SLAVEOF "" in redis.conf.erb (https://github.com/cloudfoundry/vcap-services/blob/master/redis/resources/redis.conf.erb)
So the SLAVEOF command is disable on Cloudfoudry, and so:
MONITOR
BGSAVE
BGREWRITEAOF
SLAVEOF
DEBUG
SYNC