How to wait for a spring jms listener thread to finish executing in Junit test - spring

I have a spring boot application that uses spring-JMS. Is there any way to tell the test method to wait the jms lister util it finishes executing without using latches in the actual code that will be tested?
Here is the JMS listener code:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
#Component
public class MyListener {
#Autowired
MyProcessor myProcessor;
#JmsListener(destination = "myQueue", concurrency = "1-4")
private void onMessage(Message message, QueueSession session) {
myProcessor.processMessage(message, session);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
#Component
public class MyProcessor {
public void processMessage(Message msg, QueueSession session) {
//Here I have some code.
}
}
import org.apache.activemq.command.ActiveMQTextMessage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.QueueSession;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class IntegrationTest {
#Autowired
private JmsTemplate JmsTemplate;
#Test
public void myTest() throws JMSException {
Message message = new ActiveMQTextMessage();
jmsTemplate.send("myQueue", session -> message);
/*
Here I have some testing code. How can I tell the application
to not execute this testing code until all JMS lister threads
finish executing.
*/
}
}
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.util.SocketUtils;
import javax.jms.ConnectionFactory;
#EnableJms
#Configuration
#Profile("test")
public class JmsTestConfig {
public static final String BROKER_URL =
"tcp://localhost:" + SocketUtils.findAvailableTcpPort();
#Bean
public BrokerService brokerService() throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.setPersistent(false);
brokerService.addConnector(BROKER_URL);
return brokerService;
}
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(BROKER_URL);
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
return jmsTemplate;
}
}
Note: Is it applicable to solve this without adding testing purpose code to the implementation code (MyListener and MyProcessor).

Proxy the listener and add an advice to count down a latch; here's one I did for a KafkaListener recently...
#Test
public void test() throws Exception {
this.template.send("so50214261", "foo");
assertThat(TestConfig.latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(TestConfig.received.get()).isEqualTo("foo");
}
#Configuration
public static class TestConfig {
private static final AtomicReference<String> received = new AtomicReference<>();
private static final CountDownLatch latch = new CountDownLatch(1);
#Bean
public static MethodInterceptor interceptor() {
return invocation -> {
received.set((String) invocation.getArguments()[0]);
return invocation.proceed();
};
}
#Bean
public static BeanPostProcessor listenerAdvisor() {
return new ListenerWrapper(interceptor());
}
}
public static class ListenerWrapper implements BeanPostProcessor, Ordered {
private final MethodInterceptor interceptor;
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
public ListenerWrapper(MethodInterceptor interceptor) {
this.interceptor = interceptor;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Listener) {
ProxyFactory pf = new ProxyFactory(bean);
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(this.interceptor);
advisor.addMethodName("listen");
pf.addAdvisor(advisor);
return pf.getProxy();
}
return bean;
}
}
(but you should move the countDown to after the invocation proceed()).

A method annotated with #JmsListener deletes the message after it finishes, so a good option is to read the queue for existing messages and assume the queue is empty after your method is done. Here is the piece of code for counting the messages from the queue.
private int countMessages() {
return jmsTemplate.browse(queueName, new BrowserCallback<Integer>() {
#Override
public Integer doInJms(Session session, QueueBrowser browser) throws JMSException {
return Collections.list(browser.getEnumeration()).size();
}
});
}
Following is the code for testing the countMessages() method.
jmsTemplate.convertAndSend(queueName, "***MESSAGE CONTENT***");
while (countMessages() > 0) {
log.info("number of pending messages: " + countMessages());
Thread.sleep(1_000l);
}
// continue with your logic here

I've based my solution on the answer given by Gary Russell, but rather put the CountDownLatch in an Aspect, using Spring AOP (or the spring-boot-starter-aop variant).
public class TestJMSConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(TestJMSConfiguration.class);
public static final CountDownLatch countDownLatch = new CountDownLatch(1);
#Component
#Aspect
public static class LatchCounterAspect {
#Pointcut("execution(public void be.infrabel.rocstdm.application.ROCSTDMMessageListener.onMessage(javax.jms.TextMessage))")
public void onMessageMethod() {};
#After(value = "onMessageMethod()")
public void countDownLatch() {
countDownLatch.countDown();
LOGGER.info("CountDownLatch called. Count now at: {}", countDownLatch.getCount());
}
}
A snippet of the test:
JmsTemplate jmsTemplate = new JmsTemplate(this.embeddedBrokerConnectionFactory);
jmsTemplate.convertAndSend("AQ.SOMEQUEUE.R", message);
TestJMSConfiguration.countDownLatch.await();
verify(this.listenerSpy).putResponseOnTargetQueueAlias(messageCaptor.capture());
RouteMessage outputMessage = messageCaptor.getValue();
The listenerSpy is a #SpyBean annotated field of the type of my MessageListener. The messageCaptor is a field of type ArgumentCaptor<MyMessageType> annotated with #Captor. Both of these are coming from mockito so you need to run/extend your test with both MockitoExtension (or -Runner) along with the SpringExtension (or -Runner).
My code puts an object on an outbound queue after processing the incoming message, hence the putResponseOnTargetQueueAlias method. The captor is to intercept that object and do my assertions accordingly. The same strategy could be applied to capture some other object in your logic.

Related

Spring-core with #RabbitListener annotation does not fire bounded method

I am trying to use rabbitmq #RabbitListener annotation in my methods so that whenever any message arrives, my method can get auto executed. Based on the official documentation provided here, I made the folowing config class
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class RabbitConfiguration {
public static final String QUEUE_NAME="myQueue";
public static final String EXCHANGE_NAME="my_EXCHANGE";
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setMaxConcurrentConsumers(5);
return factory;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory =
new CachingConnectionFactory("localhost");
return connectionFactory;
}
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory());
}
#Bean
public RabbitTemplate rabbitTemplate() {
return new RabbitTemplate(connectionFactory());
}
#Bean
public Queue myQueue() {
return new Queue(QUEUE_NAME);
}
#Bean
MessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
simpleMessageListenerContainer.setConnectionFactory(connectionFactory());
simpleMessageListenerContainer.setQueues(myQueue());
simpleMessageListenerContainer.setMessageListener(new RabbitMQListner());
return simpleMessageListenerContainer;
}
#Bean
FanoutExchange exchange() {
return new FanoutExchange(EXCHANGE_NAME);
}
#Bean
Binding binding(Queue queue, FanoutExchange exchange) {
return BindingBuilder.bind(queue).to(exchange);
}
#Bean
MessagingService messagingService(){
return new MessagingService();
}
}
Then from my service class I used like this:-
#Component
public class MessagingService {
public void send(String msg){
ApplicationContext context =
new AnnotationConfigApplicationContext(RabbitConfiguration.class);
AmqpTemplate template = context.getBean(AmqpTemplate.class);
template.convertAndSend(QUEUE_NAME,"Hello from template "+msg);
}
#RabbitListener(queues=QUEUE_NAME)
private void receiveMessage(String order) {
System.out.println("Hello"+order);
}
The send method is working fine. But the receiveMessage method does not print the expected output. It should print the message as soon as it arrives in the queue. Instead, When I tried to apply #EnableRabbit annotation in my configuration file, I got java.lang.ArrayStoreException exception on runtime. The method receiveMessage does not even get executed.
You can ignore typo.
You need a method annotated with #RabbitHandler as well.
Example :
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class Consumer {
#RabbitListener(queues = "myQueue")
#RabbitHandler
public void handle(String msg) {
System.out.println("RCVD :: " + msg);
}
}

How to Capture ApplicatonEvent in Spring boot integration test?

The issue is that Application Event is not being captured in Spring boot test While it works fine for files listening to event in app project.
I want to capture an ApplicationEvent in Spring boot test(don't want to do Unit testing). My goal is to capture this application event and then perform few tasks in my test to verify the end-to-end functionality. Since, the event is not being captured in test case so I am not able to write integration tests.
Please let me know what is wrong with the code.
Thanks All.
package com.example.demo;
import org.springframework.context.ApplicationEvent;
public class CacheRefreshEvent extends ApplicationEvent {
private String message;
private static final long serialVersionUID = 1L;
public CacheRefreshEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
package com.example.demo;
import org.springframework.context.ApplicationEvent;
public class CacheRefreshCompleteEvent extends ApplicationEvent {
private String message;
private static final long serialVersionUID = 1L;
public CacheRefreshCompleteEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
package com.example.demo;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
#Component
public class CaptureCacheRefreshCompleteEvent implements ApplicationListener<CacheRefreshCompleteEvent> {
private ApplicationEventPublisher applicationEventPublisher;
void applicationEvent() throws InterruptedException {
applicationEventPublisher.publishEvent(new CacheRefreshEvent(this, "event triggered from SolrUtilitiesTest()"));
Thread.sleep(5000);
System.out.println("Finished execution of test.");
}
public void onApplicationEvent(CacheRefreshCompleteEvent cs) {
System.out.println("gotcha in CaptureCachedRefreshCompleteEvent");
}
public void setApplicationEventPublisher(ApplicationEventPublisher arg0) {
this.applicationEventPublisher = arg0;
}
}
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#DirtiesContext
#SpringBootTest
class DemoApplicationTests implements ApplicationEventPublisherAware, ApplicationListener<CacheRefreshCompleteEvent> {
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Test
void applicationEvent() throws InterruptedException {
applicationEventPublisher.publishEvent(new CacheRefreshEvent(this, "event triggered from Springboot test"));
for(int i=0; i< 20; i ++) {
Thread.sleep(1000);
}
System.out.println("Finished execution of test.");
}
public void onApplicationEvent(CacheRefreshCompleteEvent cs) {
System.out.println("gotcha");
}
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher arg0) {
this.applicationEventPublisher = arg0;
}
}
One way could be to create a very simple listener with #TestComponent inside your test and autowire it as a #MockBean.
Proof of concept (tested with Spring Boot 2.2 and 2.1):
#SpringBootTest
public class PublishTest {
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#MockBean
private Consumer consumer;
#Test
public void test() {
applicationEventPublisher.publishEvent(new TestEvent(this));
// events are synchronous by default
verify(consumer).consumeEvent(any(TestEvent.class));
}
#TestComponent
private static class Consumer {
#EventListener
public void consumeEvent(TestEvent testEvent) {
}
}
private static class TestEvent extends ApplicationEvent {
public TestEvent(Object source) {
super(source);
}
}
}

spring batch integration configuration with azure service bus

I am trying to configure inbound and outbound adaptors as provided in the spring batch remote partitioning samples for Manager and worker beans. Having difficulty since they are configured in context of AMQPConnectionFactory.
However when I follow spring integration samples, there is no class which can provide Connection Factory. Help appreciated.
Below is sample code:-
import com.microsoft.azure.spring.integration.core.DefaultMessageHandler;
import com.microsoft.azure.spring.integration.core.api.CheckpointConfig;
import com.microsoft.azure.spring.integration.core.api.CheckpointMode;
import com.microsoft.azure.spring.integration.servicebus.inbound.ServiceBusQueueInboundChannelAdapter;
import com.microsoft.azure.spring.integration.servicebus.queue.ServiceBusQueueOperation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.concurrent.ListenableFutureCallback;
#Configuration
#IntegrationComponentScan
public class ManagerConfiguration {
private static final int GRID_SIZE = 3;
private static final String REQUEST_QUEUE_NAME = "digital.intg.batch.cm.request";
private static final String REPLY_QUEUE_NAME = "digital.intg.batch.cm.reply";
private static final String MANAGER_INPUT_CHANNEL = "manager.input";
private static final String MANGER_OUTPUT_CHANNEL = "manager.output";
private static final Log LOGGER = LogFactory.getLog(ManagerConfiguration.class);
private final JobBuilderFactory jobBuilderFactory;
private final RemotePartitioningManagerStepBuilderFactory managerStepBuilderFactory;
public ManagerConfiguration(JobBuilderFactory jobBuilderFactory,
RemotePartitioningManagerStepBuilderFactory managerStepBuilderFactory
) {
this.jobBuilderFactory = jobBuilderFactory;
this.managerStepBuilderFactory = managerStepBuilderFactory;
}
/*
* Configure outbound flow (requests going to workers)
*/
#Bean( name = MANGER_OUTPUT_CHANNEL )
public DirectChannel managerRequests() {
return new DirectChannel();
}
/*
* Configure inbound flow (replies coming from workers)
*/
#Bean( name = MANAGER_INPUT_CHANNEL )
public DirectChannel managerReplies() {
return new DirectChannel();
}
#Bean
public ServiceBusQueueInboundChannelAdapter managerQueueMessageChannelAdapter(
#Qualifier( MANAGER_INPUT_CHANNEL ) MessageChannel inputChannel, ServiceBusQueueOperation queueOperation) {
queueOperation.setCheckpointConfig(CheckpointConfig.builder().checkpointMode(CheckpointMode.MANUAL).build());
ServiceBusQueueInboundChannelAdapter adapter = new ServiceBusQueueInboundChannelAdapter(REPLY_QUEUE_NAME,
queueOperation);
adapter.setOutputChannel(inputChannel);
return adapter;
}
#Bean
#ServiceActivator( inputChannel = MANGER_OUTPUT_CHANNEL )
public MessageHandler managerQueueMessageSender(ServiceBusQueueOperation queueOperation) {
DefaultMessageHandler handler = new DefaultMessageHandler(REQUEST_QUEUE_NAME, queueOperation);
handler.setSendCallback(new ListenableFutureCallback<Void>() {
#Override
public void onSuccess(Void result) {
LOGGER.info("Manager Request Message was sent successfully.");
}
#Override
public void onFailure(Throwable ex) {
LOGGER.info("There was an error sending request message to worker.");
}
});
return handler;
}
#Bean
public IntegrationFlow managerOutboundFlow(MessageHandler managerQueueMessageSender) {
return IntegrationFlows
.from(managerRequests())
.handle(managerQueueMessageSender)
.get();
}
#Bean
public IntegrationFlow managerInboundFlow(ServiceBusQueueInboundChannelAdapter managerQueueMessageChannelAdapter) {
return IntegrationFlows
.from(managerQueueMessageChannelAdapter)
.channel(managerReplies())
.get();
}
/*
* Configure the manager step
*/
#Bean
public Step managerStep() {
return this.managerStepBuilderFactory.get("managerStep")
.partitioner("workerStep", new BasicPartitioner())
.gridSize(GRID_SIZE)
.outputChannel(managerRequests())
.inputChannel(managerReplies())
//.aggregator()
.build();
}
#Bean
public Job remotePartitioningJob() {
return this.jobBuilderFactory.get("remotePartitioningJob")
.start(managerStep())
.build();
}
}
The sample uses ActiveMQ because it is easily embeddable in a JVM for our tests and samples. But you can use any other broker that you want.
?? what should I inject here?
You should inject any dependency required by the queueMessageChannelAdapter handler:
.handle(queueMessageChannelAdapter)

SFTP : BeanPostProcessor interfere with #ServiceActivator and #MessagingGateway

It seems BeanPostProcessor interface implementation is having impact on #ServiceActivator. What should be the way to use BeanPostProcessor with #ServiceActivator. Thanks.
Complete logs are available here logs
Following is Java Config used for SFTP -
package com.ftp.example;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.sftp.outbound.SftpMessageHandler;
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import com.jcraft.jsch.ChannelSftp.LsEntry;
#Configuration
#EnableScheduling
#EnableAspectJAutoProxy
#EnableAsync
#IntegrationComponentScan
#EnableIntegration
#EnableBatchProcessing
#PropertySource("file:C:\\DEV\\workspace_oxygen\\ftp-example\\ftp-example.properties")
public class DependencySpringConfiguration {
private Logger LOG = LoggerFactory.getLogger(DependencySpringConfiguration.class);
#Value("${project.name}")
private String applicationName;
#Value("${${project.name}.ftp.server}")
private String server;
#Value("${${project.name}.ftp.port}")
int port;
#Value("${${project.name}.ftp.username}")
private String username;
#Value("${${project.name}.ftp.password}")
private String password;
#Value("${${project.name}.ftp.remote.directory}")
private String remoteDirectory;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public ProcessStarter processStarter() {
return new ProcessStarter();
}
/* #Bean
public LogInjector logInjector() {
return new LogInjector();
}*/
#Bean
public FTPOutService fTPOutService() {
return new FTPOutService();
}
#Bean
public SessionFactory<LsEntry> sftpSessionFactory() {
DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory();
sf.setHost(server);
sf.setPort(port);
sf.setUser(username);
sf.setPassword(password);
sf.setAllowUnknownKeys(true);
return new CachingSessionFactory<LsEntry>(sf);
}
#Bean
#ServiceActivator(inputChannel = "toSftpChannel")
public MessageHandler handler() {
SftpMessageHandler handler = new SftpMessageHandler(sftpSessionFactory());
handler.setRemoteDirectoryExpression(new LiteralExpression(remoteDirectory));
handler.setFileNameGenerator(new FileNameGenerator() {
#Override
public String generateFileName(Message<?> message) {
return "fileNameToBeFtp.txt";
}
});
return handler;
}
#MessagingGateway
public interface MyGateway {
#Gateway(requestChannel = "toSftpChannel")
void sendToSftp(File file);
}
}
And We are calling gateway object like this while doing SFTP
Main class
public class FtpExample {
public static String[] ARGS;
private static final Logger LOG = LoggerFactory.getLogger(FtpExample.class);
public static void main(String[] args) throws Exception {
ARGS = args;
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DependencySpringConfiguration.class);
ProcessStarter processStarter = ctx.getBean(ProcessStarter.class);
processStarter.startService();
}
}
Other classes -
public class ProcessStarter {
#Inject
private FTPOutService ftpOutService;
public void startService() {
ftpOutService.ftpToBbg();
}
}
public class FTPOutService {
private static Logger log = LoggerFactory.getLogger(FTPOutService.class);
#Inject
private ApplicationContext appContext;
public void ftpToBbg() {
log.info("Starting FTP out process...");
File file = null;
try {
file = new File("C:\\Temp\\log\\debug\\ftp\\priceindex\\for-upload\\ftp-example.txt.REQ");
MyGateway gateway = appContext.getBean(MyGateway.class);
gateway.sendToSftp(file);
log.info("File {} written successfully on remote server", file);
} catch (Exception e) {
log.error("Error while uploading file {}", file, e);
}
}
}
Above code is working fine unless I am not adding following bean declaration in above defined Java Config -
public LogInjector logInjector() {
return new LogInjector();
}
Above bean definition is having following implementation -
public class LogInjector implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
#Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
// make the field accessible if defined private
ReflectionUtils.makeAccessible(field);
if (field.getAnnotation(Log.class) != null) {
if (org.slf4j.Logger.class == field.getType()) {
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(bean.getClass());
field.set(bean, log);
} else if (java.util.logging.Logger.class == field.getType()) {
java.util.logging.Logger log = java.util.logging.Logger.getLogger(bean.getClass().toString());
field.set(bean, log);
}
}
}
});
return bean;
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#Documented
public #interface Log {
}
Once any BeanPostProcessor implementation is added in Java Config, it creates problem and application not able to see toSftpChannel -
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean named 'toSftpChannel' available at
org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:685)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1199)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at
org.springframework.integration.support.channel.BeanFactoryChannelResolver.resolveDestination(BeanFactoryChannelResolver.java:88)
at
org.springframework.integration.support.channel.BeanFactoryChannelResolver.resolveDestination(BeanFactoryChannelResolver.java:45)
at
org.springframework.integration.gateway.MessagingGatewaySupport.getRequestChannel(MessagingGatewaySupport.java:327)
at
org.springframework.integration.gateway.MessagingGatewaySupport.send(MessagingGatewaySupport.java:368)
at
org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:477)
at
org.springframework.integration.gateway.GatewayProxyFactoryBean.doInvoke(GatewayProxyFactoryBean.java:429)
at
org.springframework.integration.gateway.GatewayProxyFactoryBean.invoke(GatewayProxyFactoryBean.java:420)
at
org.springframework.integration.gateway.GatewayCompletableFutureProxyFactoryBean.invoke(GatewayCompletableFutureProxyFactoryBean.java:65)
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy57.sendToSftp(Unknown Source)
Looks what you have:
#Bean
public LogInjector logInjector() {
return new LogInjector();
}
If you declare BeanPostProcessors as #Bean you have to specify them with the static modifier: https://docs.spring.io/spring/docs/5.0.0.RELEASE/spring-framework-reference/core.html#beans-factorybeans-annotations
You may declare #Bean methods as static, allowing for them to be called without creating their containing configuration class as an instance. This makes particular sense when defining post-processor beans, e.g. of type BeanFactoryPostProcessor or BeanPostProcessor, since such beans will get initialized early in the container lifecycle and should avoid triggering other parts of the configuration at that point.

Method with #JmsListener not executed when setting custom message converter

Hy,
Having these three classes I have found a problem:
package xpadro.spring.jms;
import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MarshallingMessageConverter;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
#SpringBootApplication
public class JmsJavaconfigApplication {
public static void main(String[] args) {
SpringApplication.run(JmsJavaconfigApplication.class, args);
}
#Bean
public Jaxb2Marshaller jaxb2Marshaller(){
Jaxb2Marshaller jaxb = new Jaxb2Marshaller();
Class array[] = new Class[1];
array[0]= xpadro.spring.jms.model.Order.class;
jaxb.setClassesToBeBound(array);
return jaxb;
}
#Bean
#Autowired
public MarshallingMessageConverter marshallingMessageConverter( Jaxb2Marshaller marshaller){
MarshallingMessageConverter mc = new MarshallingMessageConverter();
mc.setMarshaller(marshaller);
mc.setUnmarshaller(marshaller);
return mc;
}
#Bean
#Autowired
public JmsTemplate init(MarshallingMessageConverter messageConverter,ConnectionFactory connectionFactory){
JmsTemplate template = new JmsTemplate();
template.setMessageConverter(messageConverter);
template.setConnectionFactory(connectionFactory);
return template;
}
}
Next, the model:
package xpadro.spring.jms.model;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "order")
#XmlAccessorType(XmlAccessType.FIELD)
public class Order implements Serializable {
private static final long serialVersionUID = -797586847427389162L;
#XmlElement(required = true)
private String id="";
public Order() {
}
public Order(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
Next, the listener:
package xpadro.spring.jms.listener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import xpadro.spring.jms.model.Order;
import xpadro.spring.jms.service.StoreService;
#Component
public class SimpleListener {
private final StoreService storeService;
#Autowired
public SimpleListener(StoreService storeService) {
this.storeService = storeService;
}
#JmsListener(destination = "simple.queue")
public void receiveOrder(Order order) {
storeService.registerOrder(order);
}
}
The class that sends messages:
package xpadro.spring.jms.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
import xpadro.spring.jms.model.Order;
#Service
public class ClientServiceImpl implements ClientService {
private static final String SIMPLE_QUEUE = "simple.queue";
private static final String IN_QUEUE = "in.queue";
private final JmsTemplate jmsTemplate;
#Autowired
public ClientServiceImpl(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
#Override
public void addOrder(Order order) {
//MessageRegistered with a bean
jmsTemplate.convertAndSend(SIMPLE_QUEUE, order);
}
#Override
public void registerOrder(Order order) {
//MessageRegistered with a bean
jmsTemplate.convertAndSend(IN_QUEUE, order);
}
}
The point is that listener(method receiveOrder()) is not executed when I set a custom message converter in the class JmsJavaconfigApplication. When I dont set it, spring boot sets a SimpleMessageConverter and the listener is executed.
Any wrong or missed configuration?
Thanks
You need to provide an marshalling converter to the #JmsListener to unmarshall the message, by configuring the listener container factory see the documentation.

Resources