Ensuring Spring Integration deployment's JMS listener threads are cleaned up on Tomcat undeploy - spring

I have a simple Spring Integration application which runs on Tomcat (v7.0.x) and consumes messages off a Websphere MQ Queue. When I un-deploy the WAR from the Tomcat server, the WAR un-deploys okay but, a JMS listener thread is left running on the Tomcat server which will still consume messages off the Websphere MQ Queue. I am therefore assuming that I am not handling the JMS listener clean up part of the application properly?
Here is the stack I am using:
Java 8
Tomcat 7.0.55
Spring Integration 4.0.4
Spring Integration Java Dsl 1.0.0.M3
In terms of my SI application's configurations, I have a JmsConfig class:
#Configuration
#ComponentScan
public class JmsConfig {
#Autowired
private Properties jndiProperties;
private ConnectionFactory mqConnectionFactory() throws NamingException {
Context ctx = new InitialContext(jndiProperties);
try {
MQQueueConnectionFactory connectionFactory = (MQQueueConnectionFactory)
ctx.lookup("jms/service/SERVICE_QCF");
return connectionFactory;
} finally {
ctx.close();
}
}
#Bean
public ConnectionFactory cachingConnectionFactory() throws NamingException {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setTargetConnectionFactory(mqConnectionFactory());
connectionFactory.setSessionCacheSize(10);
return connectionFactory;
}
}
I have an Integration config class:
#Configuration
#EnableIntegration
public class IntegrationConfig {
#Autowired
private ConnectionFactory cachingConnectionFactory;
#Bean
public IntegrationFlow requestFlow() {
return IntegrationFlows
.from(Jms.inboundAdapter(cachingConnectionFactory).destination(
"SERVICE_QUEUE_NAME"), c -> {
c.poller(Pollers.fixedRate(100));
})
.channel("request.service.ch").get();
}
}
Web Initialiser config class:
#Configuration
public class WebInitialiser implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext)
throws ServletException {
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(ApplicationConfig.class, JmsConfig.class,
IntegrationConfig.class, DatabaseConfig.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
}
}
During the un-deploy stage I see the following in the catalina logs which may or may not be related:
SEVERE: The web application [/service-a] appears to have started a thread named [Thread-7] but has failed to stop it. This is very likely to create a memory leak.
Is there anything that I have yet NOT set or configured or annotated in order to ensure that the deployment's JMS listener thread is cleaned up from Tomcat's JVM during the WAR's un-deploy stage?
Thanks in advance,
PM.

To ensure that JMS listener threads are cleared up upon the application's un-deploy stage, I simply created a CachingConnectionFactory bean with its targetConnectionFactory being that of the MQConnectionFactory. Then, in the Spring Integration flows, I simply pass in the cachingConnectionFactory bean to the JMS adapters instead. I've updated the configs in this post to show this. Cheers, PM.

Related

Spring 3 and Rabbit MQ integration (not Spring Boot)

I'm having difficulty getting a Spring 3 application to integrate with RabbitMQ, in order to receive messages from a queue (I do not need to send messages).
Part of the challenge is much of the documentation now relates to Spring Boot. The related Spring guide is helpful, but following the steps does not seem to work in my case. For instance, the guide includes the text:
The message listener container and receiver beans are all you need to listen for messages.
So I have setup the listener container and receiver beans with the following code.
Setting up message handler
#Component
public class CustomMessageHandler {
public void handleMessage(String text) {
System.out.println("Received: " + text);
}
}
Setting up configuration
#Configuration
public class RabbitConfig {
#Bean
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory){
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setRoutingKey("queue-name");
return rabbitTemplate;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("...host...");
connectionFactory.setPort(5671);
connectionFactory.setVirtualHost("...virtual host..");
connectionFactory.setUsername("...username...");
connectionFactory.setPassword("...password...");
return connectionFactory;
}
#Bean
public MessageListenerAdapter messageListenerAdapter(CustomMessageHandler messageHandler) {
return new MessageListenerAdapter(messageHandler, "handleMessage");
}
#Bean
public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory,
MessageListenerAdapter messageListenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setQueueNames("queue-name");
container.setConnectionFactory(connectionFactory);
container.setMessageListener(messageListenerAdapter);
return container;
}
}
Unfortunately with this setup, the application will start up, but it never triggers the message handler. The queue it is trying to read from also has one message sitting in it, waiting to be consumed.
Any ideas on something that is missing, or appears misconfigured?
Thanks to some dependency management assistance from #GaryRussell, I was able to see that the version of spring-rabbit and spring-amqp were too recent. Using the older 1.3.9.RELEASE unfortunately proved to add additional challenges.
Some other assistance came in the form of using an actual RabbitMQ Java client. This option was much simpler to implement, and avoided the dependency problems. Ultimately I needed to include the following dependency:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
And then I simply followed their documentation on creating a connection, and consuming messages.
Voila, it works!

Spring Boot Kafka with manual ack

Referring to Spring kafka doc, I am trying to implement a spring boot application which listens to a kafka topic and acks manually.
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#KafkaListener(topics = "sample_log", containerFactory = "kafkaManualAckListenerContainerFactory")
public void listen(ConsumerRecord<?, ?> cr, Acknowledgment ack) throws Exception {
process(cr);
ack.acknowledge();
}
}
But when I ran the application, it shows:
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean named 'kafkaManualAckListenerContainerFactory' that could not be found.
Action:
Consider defining a bean named 'kafkaManualAckListenerContainerFactory' in your configuration.
As far as I'm concerned, Spring Kafka may have defined kafkaManualAckListenerContainerFactory bean since it is a provided feature. How could I fix the error WITHOUT manually define the bean?
You don’t need that containerFactory = "kafkaManualAckListenerContainerFactory" configuration. Just remove it altogether and rely on the provided by Spring Boot auto-configuration for Kafka: https://docs.spring.io/spring-boot/docs/2.0.1.RELEASE/reference/htmlsingle/#boot-features-kafka

Spring Boot JMS AutoStartup

I am trying to start/stop manually JMS listeners in my Spring Boot App. I am currently using the following configuration to my container factory:
#EnableJms
public class ConfigJms {
...
#Bean(name = "queueContainerFactory")
public JmsListenerContainerFactory<?> queueContainerFactory(ConnectionFactory cf) {
ActiveMQConnectionFactory amqCf = (ActiveMQConnectionFactory) cf;
amqCf.setTrustAllPackages(true);
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(amqCf);
**factory.setAutoStartup(false);**
return factory;
}
...
}
After testing factory.setAutoStartup(false); I am quite confused because even indicating to do not start any listener for this factory container, the listeners are already registered and started when the context starts.
I tested this situation by using a jmsListenerEndpointRegistry.
jmsListenerEndpointRegistry.isAutoStartup() is true and
jmsListenerEndpointRegistry. isRunning () is true before execute jmsListenerEndpointRegistry.start();
Is it necessary to configure anything else? Maybe I am omitting to override some auto-configuration.
EDIT 1: Invalid status of JmsListenerEndpointRegistry listeners
I detected a couple of inconsistences in my beans:
jmsListenerEndpointRegistry.getListenerContainerIds().size() is always 0.
jmsListenerEndpointRegistry.isAutoStartup() is just a return true method.
Even if I register a couple of listeners with annotations like this:
#JmsListener(containerFactory="queueContainerFactory", destination = "${dest}")
jmsListenerEndpointRegistry does not show information about these listeners status but they are connected to ActiveMQ on startup. (Checking the ActiveMQ admin console)
EDIT 2: #JmsListener starts even auto-startup is set to false
I checked the jmsListenerEndpointRegistry for each container and I do not know if this is a bug or I am not correctly defining the configuration. However, I am just defining the container factory as explained before with AUTO-START set to false and the both listeners are started and consuming messages (running).
From my Log file:
jmsListenerEndpointRegistry ID <org.springframework.jms.JmsListenerEndpointContainer#1>, Auto-Startup <false>, Running <true>
jmsListenerEndpointRegistry ID <org.springframework.jms.JmsListenerEndpointContainer#0>, Auto-Startup <false>, Running <true>
You must have something else going on - I just wrote a quick boot app (1.4.1) and the container is not started...
#SpringBootApplication
public class So39654027Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So39654027Application.class, args);
JmsListenerEndpointRegistry reg = context.getBean(JmsListenerEndpointRegistry.class);
MessageListenerContainer listenerContainer = reg.getListenerContainer("foo");
System.out.println(listenerContainer.isRunning());
}
#Bean(name = "queueContainerFactory")
public JmsListenerContainerFactory<?> queueContainerFactory(ConnectionFactory cf) {
ActiveMQConnectionFactory amqCf = (ActiveMQConnectionFactory) cf;
amqCf.setTrustAllPackages(true);
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(amqCf);
factory.setAutoStartup(false);
return factory;
}
#JmsListener(id="foo", destination = "so39654027", containerFactory = "queueContainerFactory")
public void listen(String foo) {
System.out.println(foo);
}
}
and...
2016-09-23 09:24:33.428 INFO 97907 --- [ main] com.example.So39654027Application : Started So39654027Application in 1.193 seconds (JVM running for 2.012)
false
I suggest you use a debugger in the container's start() method to see why it's being started.
Order is important, factory.setAutoStartup(autoStartup) after configure.
#Bean
public JmsListenerContainerFactory<?> ShipmentListenerFactory(#Qualifier("GSUBCachingConnectionFactory") CachingConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
// Added ability to disable not start listener
boolean autoStartup = env.getProperty("app-env.CKPT_QUEUE_AUTO_START",Boolean.class,true);
log.info("[MQ] CKPT_QUEUE_AUTO_START:{}",autoStartup);
configurer.configure(factory, connectionFactory);
factory.setAutoStartup(autoStartup);
// You could still override some of Boot's default if necessary.
return factory;
}

Set ConnectionFactory for Camel JMS Producer: camel-jms Vs camel-sjms

Ciao, my basic requirement is to have a route where I can send a message and this is put on a JMS Queue. The camel context run in a JavaEE 6 container namely JBoss AS 7.1.1 so it's HornetQ for JMS which ships with it; I start the context via bootstrap singleton but I don't use the camel-cdi. So far I've been using camel-jms component, but now I'm looking to migrate to the camel-sjms if possible because springless.
My question is: what is the proper way to configure the ConnectionFactory for camel-sjms in this JavaEE scenario, please?
With the camel-jms I could put this in the endpoint URL, as simple as .to("jms:myQueue?connectionFactory=#ConnectionFactory"). With the camel-sjms instead it seems to me that I need to create an instance of the SJMSComponent myself, set the connectionFactory, and set this instance in the camel context before starting it.
I have code below for the camel-jms Vs camel-sjms case, and I would like to know if I "migrated" the setting of the ConnectionFactory correctly. Thanks.
For camel-jms this was done as:
#Singleton
#Startup
public class CamelBootstrap {
private CamelContext camelContext;
private ProducerTemplate producerTemplate;
public CamelContext getCamelContext() {
return camelContext;
}
public ProducerTemplate getProducerTemplate() {
return producerTemplate;
}
#PostConstruct
public void init() throws Exception {
camelContext = new DefaultCamelContext();
camelContext.addRoutes(new MyCamelRoutes());
camelContext.start();
producerTemplate = camelContext.createProducerTemplate();
}
}
Nothing special, and in the MyCamelRoutes I could do route configuration using:
.to("jms:myQueue?connectionFactory=#ConnectionFactory")
For camel-sjms now I have to modify the bootstrap singleton with:
#Singleton
#Startup
public class CamelBootstrap {
#Resource(mappedName="java:/ConnectionFactory")
private ConnectionFactory connectionFactory;
private CamelContext camelContext;
private ProducerTemplate producerTemplate;
public CamelContext getCamelContext() {
return camelContext;
}
public ProducerTemplate getProducerTemplate() {
return producerTemplate;
}
#PostConstruct
public void init() throws Exception {
camelContext = new DefaultCamelContext();
SjmsComponent sjms = new SjmsComponent();
sjms.setConnectionFactory(connectionFactory);
camelContext.addComponent("sjms", sjms);
camelContext.addRoutes(new MyCamelRoutes());
camelContext.start();
producerTemplate = camelContext.createProducerTemplate();
}
}
and please notice #Resource for the connectionFactory this is passed as a reference to the SjmsComponent instance, which is passed to the camelContext. And then in the MyCamelRoutes I could use the sjms while do route configuration using:
.to("sjms:myQueue")
The code seems to work correctly in both scenario, but as I understand the configuration of the ConnectionFactory is quite susceptible of performance issue if not done correctly, therefore I prefer to ask if I migrated to the camel-sjms correctly for my JavaEE scenario. Thanks again
Performance issues are likely to happend if you don't do caching/pooling of JMS resources. Caching is typically configured by wrapping a ConnectionFactory in some Caching ConnectionFactory library - or by handing over the control to the application server.
Camel SJMS includes built-in pooling. However, if you have a container managed resource to handle JMS connections, you should probably consider using it. SJMS has some facilities to deal with that, ConncetionResource instead of ConnectionFactory.

spring-boot configure non exposed properties

I am using spring-boot to configure jms and activemq connectivity. Due to a defect in activemq I need to set the idle timeout on the PooledConnectionFactory. This configuration is not exposed by spring-boot. How do I set it?
I have a #Bean to create a messageListenerContainer which has the connectionFactory as an argument. I can instanceof check the factory and configure it here but this seems not the correct way.
Downcasting to PooledConnectionFactory and calling setIdleTimeout is a perfectly reasonable approach, in my opinion.
If you'd prefer not to do it as part of the creation of the message listener container, you could declare your own ConnectionFactory bean while still making use of ActiveMQProperties. Something like this:
#Configuration
#EnableConfigurationProperties(ActiveMQProperties.class)
class CustomActiveMQConnectionFactoryConfiguration {
#Autowired
private ActiveMQProperties properties;
#Bean
public ConnectionFactory jmsConnectionFactory() {
ConnectionFactory connectionFactory = this.properties.createConnectionFactory();
if (connectionFactory instanceof PooledConnectionFactory) {
((PooledConnectionFactory) connectionFactory).setIdleTimeout(1000);
}
return connectionFactory;
}
}

Resources