I cannot override containerFactory on RabbitListener - spring-boot

I am facing overridden issue and more precisely in the containerFactory in one of my RabbitListener.
Let's say that I have projectA and one of its dependencies (library B) instantiates the following configuration in runtime.
#Configuration
public class AmqpConfiguration {
#Bean
public static BeanPostProcessor bpp() {
return new BeanPostProcessor() {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RabbitTemplate) {
RabbitTemplate rabbitTemplate = (RabbitTemplate) bean;
rabbitTemplate.addBeforePublishPostProcessors(new LogRequestBeforePublishPostProcessor());
} else if (bean instanceof AbstractRabbitListenerContainerFactory) {
AbstractRabbitListenerContainerFactory<?> rabbitContainerFactory = (AbstractRabbitListenerContainerFactory<?>) bean;
rabbitContainerFactory.setAfterReceivePostProcessors(new LogRequestAfterReceivePostProcessor());
}
return bean;
}
};
}
}
As you can understand, the above bean injects custom implementations of MessagePostProcessor in RabbitTamplate and AbstractRabbitListenerContainerFactory instances.
My problem is that I want to extend or override MessagePostProcessor of AbstractRabbitListenerContainerFactory that has been setup in the code below.
rabbitContainerFactory.setAfterReceivePostProcessors(new LogRequestAfterReceivePostProcessor());
To overcome this situation, I tried to create a new instance of SimpleRabbitListenerContainerFactory and pass it to my RabbitListener in projectA but unfortunately I did not make it work. Using debug mode I saw that libraryB's MessagePostProcessor was called.
See my test code below.
#Configuration
public class LoggingContainerConfiguration {
#Bean(name = "rabbitListenerContainerFactoryNew")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory, MessageConverter objJsonMessageConverter, ObjectMapper objectMapper) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setMessageConverter(objJsonMessageConverter);
factory.setAfterReceivePostProcessors(new LogPaymentEventDTOAfterReceivePostProcessor(objectMapper));
return factory;
}
}
#RabbitListener(containerFactory = "rabbitListenerContainerFactoryNew",
queues = {"test1", "test2"})
public void listener(Pojo pojo) {
...
}
Do you have any suggestions how to override MessagePostProcessor of connectionFactory in my RabbitListener ?
Thank you in advance.

Project B's BPP modifies ALL instances; not by bean name.
Either change it to only modify its own beans, or you can add a SmartInitializingSingleton to re-modify your bean after the BPP has run.

Related

Redelivery Policy Is Not Working in Activemq Spring Boot

I have used below configuration
#SpringBootApplication
#EnableScheduling
public class NotificationApplication {
#Value("${jms.broker.endpoint}")
private String brokerUrl;
#Autowired
private Environment env;
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(NotificationApplication.class, args);
RecordReader abc = ctx.getBean(RecordReader.class);
abc.readNotifications();
}
#Bean
public ActiveMQConnectionFactory activeMQConnectionFactory() {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrl);
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(env.getProperty("jms.redelivery.maximum", Integer.class));
factory.setRedeliveryPolicy(redeliveryPolicy);
factory.setTrustedPackages(Arrays.asList("com.lms.notification"));
return factory;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() throws Throwable {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(activeMQConnectionFactory());
factory.setMessageConverter(jacksonJmsMessageConverter());
factory.setConcurrency(env.getProperty("jms.connections.concurrent"));
return factory;
}
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(activeMQConnectionFactory());
template.setMessageConverter(jacksonJmsMessageConverter());
return template;
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
}
The issue is the Redelivery policy is not working as I have defined in activeMQConnectionFactory bean. Means I have set maximum redelivery 1, but is not being redelivered in case of exception in listener. Also in case of exception in listener it should go to the default DLQ, which is also not happening. But if I comment the jmsListenerContainerFactory bean all works fine.
I am not able to identify why this is happening. Can any one look into this what wrong I am doing?
I am using Activemq 5.16.1
Thanks

Spring Boot RabbitMQ Null Pointer Exception Error

I am using RabbitMQ with Spring Boot to broker messages between two services. I am able to receive the message and format it but when I call a service class in the onMessage method, I get a null pointer exception error. Here is my message listener class which receives the message
public class QueueListener implements MessageListener{
#Autowired
private QueueProcessor queueProcessor;
#Override
public void onMessage(Message message) {
String msg = new String(message.getBody());
String output = msg.replaceAll("\\\\", "");
String jsonified = output.substring(1, output.length()-1);
JSONArray obj = new JSONArray(jsonified);
queueProcessor.processMessage(obj);
}
}
Calling the method processMessage throws null pointer exception
Can someone point to me what I ma doing wrong?
I found out the issue was in the RabbitMqConfig class. Here is the code which was causing the error:
#Configuration
public class RabbitMqConfig {
private static final String QUEUE_NAME = "my.queue.name";
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("<url.to.rabbit>");
connectionFactory.setUsername("<username>");
connectionFactory.setPassword("<password>");
return connectionFactory;;
}
#Bean
public Queue simpleQueue() {
return new Queue(QUEUE_NAME);
}
#Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(QUEUE_NAME);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public SimpleMessageListenerContainer userListenerContainer() {
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer();
listenerContainer.setConnectionFactory(connectionFactory());
listenerContainer.setQueues(simpleQueue());
listenerContainer.setMessageConverter(jsonMessageConverter());
listenerContainer.setMessageListener(new QueueListener());
listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
return listenerContainer;
}
}
The line listenerContainer.setMessageListener(new QueueListener()); was the source of the error. I solved it by Autowiring the class instead of using new. Here is the working code
#Configuration
public class RabbitMqConfig {
private static final String QUEUE_NAME = "my.queue.name";
#Autowired
private QueueListener queueListener;
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("<url.to.rabbit>");
connectionFactory.setUsername("<username>");
connectionFactory.setPassword("<password>");
return connectionFactory;
}
#Bean
public Queue simpleQueue() {
return new Queue(QUEUE_NAME);
}
#Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(QUEUE_NAME);
template.setMessageConverter(jsonMessageConverter());
return template;
}
/*#Bean
public SimpleMessageListenerContainer userListenerContainer() {
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer();
listenerContainer.setConnectionFactory(connectionFactory());
listenerContainer.setQueues(simpleQueue());
listenerContainer.setMessageConverter(jsonMessageConverter());
listenerContainer.setMessageListener(queueListener);
listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
return listenerContainer;
}
}
Hope this helps someone else
Make sure the QueueListener is a component class or service class that can be managed by the Spring IoC. Otherwise, the config class cannot make this a bean out of the box, since this is just a normal Java class that need to be in the container #runtime.
So when u write new QueueListener() in yr config class, then the Java class is not in the SpringContext at the time when the config class is instantiated and is therefore null.
Hope this helps clear out some of this issue!

Message conversion after retrieving it from activemq topic

I wanted to implement messaging between microservices using ActiveMQ as a broker, but after setting everyting as should be, one thing got me stuck. Before I describe the problem, here is I am approaching messaging:
Producer config:
#Slf4j
#Configuration
#EnableJms
public class JmsConfig {
public static final String EBAY_TOPIC = "ebay.topic";
#Bean
public ActiveMQTopic destinationTopic() {
return new ActiveMQTopic(EBAY_TOPIC);
}
#Bean
public JmsListenerContainerFactory<?> connectionFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
jmsTemplate.setDefaultDestinationName(EBAY_TOPIC);
jmsTemplate.setConnectionFactory(connectionFactory);
jmsTemplate.setMessageConverter(messageConverter);
jmsTemplate.setPubSubDomain(true);
return jmsTemplate;
}
}
Producer sending message:
#RequiredArgsConstructor
#Slf4j
#RestController
#RequestMapping("/message")
public class IntercommunicationController {
private final JmsTemplate jmsTemplate;
#PostMapping("/send")
public void sendMessageToOtherService(#RequestBody Message message) {
jmsTemplate.convertAndSend(JmsConfig.EBAY_TOPIC, message);
}
}
Receiver config:
#Configuration
#EnableJms
public class JmsConfig {
public static final String EBAY_TOPIC = "ebay.topic";
#Bean
public JmsListenerContainerFactory<?> connectionFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer,
ErrorHandler errorHandler) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setErrorHandler(errorHandler);
factory.setPubSubDomain(true);
return factory;
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
}
Receiver implementation:
#Slf4j
#Component
public class MessageListener {
#JmsListener(destination = JmsConfig.EBAY_TOPIC, containerFactory = "connectionFactory")
public void receiveMessage(#Payload Message receivedMessage) {
log.info("Got message saying {}", receivedMessage);
}
}
POJO which is being sent:
#NoArgsConstructor
#Data
public class Message {
String content;
String from;
}
Problem:
I'm getting error once I try to POST object through json and send it on topic. Once message get to the topic, the consumer can't handle deserialization of the message as in TypeIdPropertyName is different package path of the object which I'm sending to the one I'm trying to receive, they are 1:1 in both applications, but I'm getting:
2018-03-20 16:32:08.601 ERROR 1568 --- [enerContainer-1] c.g.g.s.c.config.MessageErrorHandler : Listener method 'public void com.gft.graduate2018.sabb.client.listener.MessageListener.receiveMessage(client.domain.Message)' threw exception; nested exception is org.springframework.jms.support.converter.MessageConversionException: Failed to resolve type id [backend.model.dtos.Message]; nested exception is java.lang.ClassNotFoundException: backend.model.dtos.Message
What's the proper way of addressing that problem ? I could end up writing custom parser from string to object but probably that would not be the best solution. Hopefully there is someone who dealt with it before and can help resolve that problem :)
See...
/**
* Specify mappings from type ids to Java classes, if desired.
* This allows for synthetic ids in the type id message property,
* instead of transferring Java class names.
* <p>Default is no custom mappings, i.e. transferring raw Java class names.
* #param typeIdMappings a Map with type id values as keys and Java classes as values
*/
public void setTypeIdMappings(Map<String, Class<?>> typeIdMappings) {
...
}
...on the converter.
Set the mappings on both sides, so the __type header just contains a token that's mapped from the class name on the sending side and to the class name on the receiving side.

How to use user defined database proxy in #DataJpaTest

We need to track database metrics so we are using datasource-proxy to track this to integrate the same in spring boot project we have created custom datasource
as below
#Component
#Slf4j
#ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceBeanConfig
{
public DataSource actualDataSource()
{
EmbeddedDatabaseBuilder databaseBuilder = new EmbeddedDatabaseBuilder();
return databaseBuilder.setType(EmbeddedDatabaseType.H2).build();
}
#Bean
#Primary
public DataSource dataSource() {
// use pretty formatted query with multiline enabled
PrettyQueryEntryCreator creator = new PrettyQueryEntryCreator();
creator.setMultiline(true);
log.info("Inside Proxy Creation");
SystemOutQueryLoggingListener listener = new SystemOutQueryLoggingListener();
listener.setQueryLogEntryCreator(creator);
return ProxyDataSourceBuilder
.create(actualDataSource())
.countQuery()
.name("MyDS")
.listener(listener)
.build();
}
}
When we run main application datasource-proxy is picked up but when we use #DataJpaTest it is not picking up. How to enable datasource-proxy in JUNIT test cases?
Edit::
Using Spring BeanPostProcessor to configure Proxy DataSource
#Slf4j
#Configuration
public class DataSourceBeanConfig implements BeanPostProcessor
{
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException
{
if (bean instanceof DataSource)
{
System.out.println("AfterInitialization : " + beanName);
// use pretty formatted query with multiline enabled
PrettyQueryEntryCreator creator = new PrettyQueryEntryCreator();
creator.setMultiline(true);
log.info("Inside Proxy Creation");
SystemOutQueryLoggingListener listener = new SystemOutQueryLoggingListener();
listener.setQueryLogEntryCreator(creator);
return ProxyDataSourceBuilder.create((DataSource) bean).countQuery()
.name("MyDS").listener(listener).build();
}
return bean; // you can return any other object as well
}
}
Here is the solution we need to create TestConfiguration to use in #DataJpaTest
#RunWith(SpringRunner.class)
#DataJpaTest
public class DataTestJPA
{
#TestConfiguration
static class ProxyDataSourceConfig implements BeanPostProcessor
{
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException
{
if (bean instanceof DataSource)
{
return ProxyDataSourceBuilder
.create((DataSource) bean)
.countQuery()
.name("MyDS")
.build();
// #formatter:on
}
return bean; // you can return any other object as well
}
}
}

Error using Spring AOP with Dynamic Proxy with JMS Template on Weblogic 12.2.1

I'm getting an error when I try to use in the same project Spring Template JMS to messaging to ActiveMQ and Spring AOP to auditing and handle exceptions on Weblogic 12.2.1. Error occurs on server startup.
If I config aspect to use CGLIB, I got exceptions from Weblogic, and I prefer, if it's possible to maintain using of Dynamic Proxy. Does anyone already had this problem or have any idea what could cause it?
My aspect config class:
#Configuration
#EnableAspectJAutoProxy
#lombok.extern.slf4j.Slf4j
public class AspectConfig {
#Bean
public LoggingErrorAspect loggingErrorAspect(){
return new LoggingErrorAspect();
}
}
Message Listener config class:
#Configuration
#EnableJms
#lombok.extern.slf4j.Slf4j
public class MessagingListenerConfig {
#Autowired
ConnectionFactory connectionFactory;
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setConcurrency("1-1");
return factory;
}
}
Stack:
weblogic.application.ModuleException:
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean
named
'org.springframework.jms.config.internalJmsListenerEndpointRegistry'
is expected to be of type
'org.springframework.jms.config.JmsListenerEndpointRegistry' but was
actually of type 'com.sun.proxy.$Proxy213'
You don't show where you are injecting the JmsListenerEndpointRegistry (or RabbitTemplate) but you have to inject by interface when you proxy with JDK proxies.
Why are you advising the registry - what are you trying to achieve with that? It doesn't provide many interfaces so it can't be referenced once proxied, except by DisposableBean and SmartLifecycle.
The RabbitTemplate can be proxied but you need to inject RabbitOperations, not RabbitTemplate.
I fix it the problem. I had to change the broker to AMQP RabbitMQ, and I needed to use interface instead of classes in all my config. After it, startup load works and aspect too.
Here is Aspect Config and Messaging Config.
AspectConfig:
#Configuration
#EnableAspectJAutoProxy
#lombok.extern.slf4j.Slf4j
public class AspectConfig {
#Bean
public LoggingErrorAspect loggingErrorAspect(){
return new LoggingErrorAspect();
}
}
MessagingConfig:
#Configuration
#PropertySources({ #PropertySource("classpath:/config/messaging.properties") })
#ComponentScan("br.com.aegea.scab.notification")
#lombok.extern.slf4j.Slf4j
public class MessagingConfig {
public static final String ERROR_QUEUE = "ERROR_QUEUE";
public static final String EMAIL_QUEUE = "EMAIL_QUEUE";
#Autowired
private Environment environment;
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(environment.getProperty("spring.rabbitMQ.host"));
connectionFactory.setUsername(environment.getProperty("spring.rabbitMQ.user"));
connectionFactory.setPassword(environment.getProperty("spring.rabbitMQ.password"));
return connectionFactory;
}
#Bean
public MessageConverter jsonMessageConverter() {
return new JsonMessageConverter();
}
#Bean
public RabbitOperations emailRabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(EMAIL_QUEUE);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public MessageListenerContainer listenerContainer() {
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer();
listenerContainer.setConnectionFactory(connectionFactory());
listenerContainer.setQueueNames(EMAIL_QUEUE);
listenerContainer.setMessageConverter(jsonMessageConverter());
listenerContainer.setMessageListener(messageReceiver());
listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
return listenerContainer;
}
#Bean
public MessageListener messageReceiver() {
return new MessageReceiver();
}
}

Resources