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

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();
}
}

Related

I cannot override containerFactory on RabbitListener

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.

Spring Boot JMS integration

I am going through the Spring Boot JMS guide. Here the JMSTemplate is initialized in the main method using context.getBean. How can I initialize JMSTemplate outside the main method (i.e. in a separate class)?
You can have a separate config class for creating your jms configuration as follows :
#Configuration
public class JmsConfig {
#Bean
public MessageConverter messageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
}
Once you are done with configuration you can fetch the JMSTemplate bean from any class, for example ;
#Component
public class HelloSender {
private final JmsTemplate jmsTemplate;
public HelloSender(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
}
Here your JMSTemplate bean is getting autowired using constructor injection.

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!

Unit Test Error creating bean with name "amqAdmin" while testing spring integration TCP component

I'm writing a sidecar micro service that talks TCP to a legacy app and uses rabbitMQ on the other side. I'm just getting started and writing tests as I go along to better understand how everything works as I'm new to Spring. My app builds, deploys, and runs fine.
However, writing clean tests has been a bit more complicated. I have been patterning my code off the spring-integration tcp basic examples. I started with having a mock TCPServer to test my code defined outside my test class. But the mock TCPServer kept being built for every test class. So I moved it into the TCPGatewayTest class similar to the example I found.
This led to some missing bean issues which led me to add a #ContextConfiguration annotation which has gotten me further. But now I have other missing beans. I'm sure I'm messing up my ApplicationContext with #ContextConfiguration. Is there a better way of doing this with a different annotation or doing it slightly different? I'm not going the xml route and would like to steer clear of it if possible.
The familiar no qualifying bean error:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.amqp.rabbit.connection.ConnectionFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
My Test Class is the following
package org.inc.imo;
#ComponentScan("org.inc")
#ContextConfiguration(classes = {TCPGatewayTest.TCPServerMock.class,
org.inc.imo.Configuration.TCPConfig.class,
org.inc.imo.Configuration.RabbitConfig.class })
#TestPropertySource(locations= "classpath:test.properties")
#RunWith(SpringRunner.class)
#SpringBootTest
public class TCPGatewayTest {
#Autowired
private TCPGateway gateway;
#Autowired
AbstractServerConnectionFactory crLfServer;
#Before
public void setup() {
TestingUtilities.waitListening(this.crLfServer, 10000L);
}
#Test
public void testConnectionToMockServer() {
String result = gateway.send("Hello World");
assertEquals("HELLO WORLD", result);
}
#Configuration
#MessageEndpoint
public static class TCPServerMock {
#Value("${imo.port}")
private int port;
#Bean()
public AbstractServerConnectionFactory serverCF() {
return new TcpNetServerConnectionFactory(this.port);
}
#Transformer(inputChannel="fromTcp", outputChannel="toEcho")
public String convert(byte[] bytes) {
return new String(bytes);
}
#ServiceActivator(inputChannel="toEcho")
public String upCase(String in) {
return in.toUpperCase();
}
#Bean
public TcpInboundGateway tcpInGate(AbstractServerConnectionFactory connectionFactory) {
TcpInboundGateway inGate = new TcpInboundGateway();
inGate.setConnectionFactory(connectionFactory);
inGate.setRequestChannel(fromTcp());
return inGate;
}
#Bean
public MessageChannel fromTcp() {
return new DirectChannel();
}
}
}
Rabbit Config Class
package org.inc.imo.configuration;
#EnableIntegration
#IntegrationComponentScan
#ComponentScan
#Configuration
public class RabbitConfig {
public final static String IMO_REQUEST_JEA = "imo.request.jea";
public final static String IMO_REQUEST_INFO = "imo.request.info";
#Bean
public AmqpAdmin amqpAdmin(final ConnectionFactory connectionFactory) {
RabbitAdmin admin = new RabbitAdmin(connectionFactory);
admin.declareQueue(jeaRequestQueue());
admin.declareQueue(infoRequestQueue());
return admin;
}
#Bean
public Queue jeaRequestQueue() {
return new Queue(IMO_REQUEST_JEA);
}
#Bean
public Queue infoRequestQueue() {
return new Queue(IMO_REQUEST_INFO);
}
#Bean
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
#Bean
public ObjectWriter objectWriter() {
return new ObjectMapper().writer();
}
}
TCPConfig class
package org.inc.imo.configuration;
#EnableIntegration
#IntegrationComponentScan
#ComponentScan
#Configuration
public class TCPConfig {
#Value("${imo.hostname}")
private String host;
#Value("${imo.port}")
private int port;
private static MessageChannel sendChannel;
private static MessageChannel replyChannel;
#Bean
public MessageChannel replyChannel() {
replyChannel = new DirectChannel();
return replyChannel;
}
#Bean(name="sendChannel")
public MessageChannel sendChannel() {
MessageChannel directChannel = new DirectChannel();
sendChannel = directChannel;
return directChannel;
}
#Bean
public TcpNetClientConnectionFactory connectionFactory() {
TcpNetClientConnectionFactory connectionFactory = new TcpNetClientConnectionFactory(host, port);
connectionFactory.setSingleUse(false);
return connectionFactory;
}
#Bean
#ServiceActivator(inputChannel = "sendChannel")
public TcpOutboundGateway tcpOutboundGateway() {
TcpOutboundGateway tcpOutboundGateway = new TcpOutboundGateway();
tcpOutboundGateway.setConnectionFactory(connectionFactory());
tcpOutboundGateway.setReplyChannel(this.replyChannel());
tcpOutboundGateway.setRequiresReply(true);
return tcpOutboundGateway;
}
}
TCPGateway Interface
package org.inc.imo.Domain;
#MessagingGateway(defaultRequestChannel = "sendChannel")
public interface TCPGateway {
String send(String message);
}
The exception is like:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.amqp.rabbit.connection.ConnectionFactory' available: expected at least 1 bean which qualifies as autowire candidate.
And the suffered bean is like:
#Bean
public AmqpAdmin amqpAdmin(final ConnectionFactory connectionFactory) {
RabbitAdmin admin = new RabbitAdmin(connectionFactory);
admin.declareQueue(jeaRequestQueue());
admin.declareQueue(infoRequestQueue());
return admin;
}
So, you request here org.springframework.amqp.rabbit.connection.ConnectionFactory bean injection, but there is no one.
If you simply test against the local RabbitMQ, there is just enough to add one more bean:
#Bean
ConnectionFactory connectionFactory() {
return new CachingConnectionFactory();
}
and your AmqpAdmin will be able to connect there.

Access connectionfactory inside messageListener onMessage

I am creating a rabbitmq messageListener and would like to be able to access the connectionfactory configuration while in the onMessage method, is that possible? It would be useful for logging and other details. Being able to log the vhost from which the message was delivered would be helpful and it is not available in the message itself. Here is my consumer and config
public class Consumer implements MessageListener {
#Override
public void onMessage(Message message) {
//how can I get the connection factory configuration when a message is sent?
}
here is the config
{
#Configuration
#EnableAutoConfiguration
public class RabbitConfig {
private static final String SIMPLE_MESSAGE_QUEUE = "qDLX2.dlq";
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("server");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("ad,om");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("vhost1");
return connectionFactory;
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
return template;
}
#Autowired
private Consumer consumer;
#Bean
public SimpleMessageListenerContainer listenerContainer() {
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer();
listenerContainer.setConnectionFactory(connectionFactory());
listenerContainer.setQueueNames(SIMPLE_MESSAGE_QUEUE);
listenerContainer.setMessageListener(consumer);
listenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
return listenerContainer;
}
}
Thanks
Gregg
There's no standard way of doing that.
You could inject the connection factory into the consumer field in your listenerContainer bean definition...
listenerContainer.setMessageListener(this.consumer);
this.consumer.setConnectionFactory(connectionFactory());
You could probably #Autowire it too.
You can then use getRabbitConnectionFactory().getVirtualHost() to access the vhost.

Resources