Spring configuration for Radis, how the beans get wired? - spring

In the following example from Getting started guide of Spring how the container bean gets connectionFactory? Does Spring Boot supplies a connectionFactory on its own?
Getting Started Messaging with Spring Redis
There are 5 beans :
latch
receiver
listenerAdapter
template
container
latch gets created first. Then receiver because receiver constructor needs latch.Then listenerAdapter because it needs receiver.Both template and container need connectionFactory.
In the code I do not find any method with name connectionFactory and annotated with #Bean.
#SpringBootApplication
public class Application {
#Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic("chat"));
return container;
}
#Bean
MessageListenerAdapter listenerAdapter(Receiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
#Bean
Receiver receiver(CountDownLatch latch) {
return new Receiver(latch);
}
#Bean
CountDownLatch latch() {
return new CountDownLatch(1);
}
#Bean
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}

It's in the classpath of your project, this is what spring boot does

Related

How to create JMS MessageListenerContainer on a list of ConnectionFactory

The following configuration creates a MessageListenerContainer on one ConnectionFactory.
#Configuration
public class MyConfig {
#Bean
public MessageListenerContainer myListenerContainer() {
DefaultMessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer();
messageListenerContainer.setConnectionFactory(myConnectionFactory1);
[...]
return messageListenerContainer;
}
}
I want to create the same configuration of MessageListenerContainer but with differents ConnectionFactory (pointing on differents queues managers).
I've tried to return a list of MessageListenerContainer (MessageListenerContainer[] or List<MessageListenerContainer> with or without a #Qualifier) but new messages are ignored.
How could I manage the MessageListenerContainer creation on a list of ConnectionFactory?
There's another way to create multiple MessageListenerContainer, instead of creating MessageListenerContainer you create multiple JmsListenerContainerFactory and set the bean name in JmsListener.
#EnableJms
class JmsConfiguration{
Bean
public JmsListenerContainerFactory jmsListenerContainerFactory(){
// create jms listener
}
#Bean
public JmsListenerContainerFactory jmsListenerContainerFactory2(){
// create jms listener
}
}
Two factory beans are created here, jmsListenerContainerFactory and jmsListenerContainerFactory2, now you can use these factory beans in JmsListener to denote which bean to be used.
#Component
class JmsListeners {
#JmsListener(containerFactory="jmsListenerContainerFactory")
public void onMessage(...){
}
#JmsListener(containerFactory="jmsListenerContainerFactory2")
public void onMessage2(...){
}
}
You can register the beans dynamically with the application context.
#Component
class Configurer {
Configurer (GenericApplicationContext context) {
for (i = 0; ...) {
ConnectionFactory cf = ...
context.registerBean("cf" + i, ConnectionFactory.class, () -> cf);
context.getBean("cf" + i); // to initialize it
DefaultMessageListenerContainer container = ...
context.registerBean("container" + i, ...);
context.getBean("container" + i, ...
}
}
}

RabbitMQ Spring Boot AMQP - consume with concurrent threads

I want my app to handle multiple messages received from RabbitMQ concurrently.
I have tried probably all the google-page-1 solutions but it won't work.
Here is my setup:
POM.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
</parent>
.
.
.
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
application.properties:
#############################
# RabbitMQ #
#############################
#AMQP RabbitMQ configuration
spring.rabbitmq.host=zzzzzzzz
spring.rabbitmq.port=5672
spring.rabbitmq.username=zzzzzzz
spring.rabbitmq.password=zzzzzzz
#Rabbit component names
com.cp.neworder.queue.name = new-order-queue-stg
com.cp.neworder.queue.exchange = new-order-exchange-stg
com.cp.completedorder.queue.name = completed-order-queue
com.cp.completedorder.queue.exchange = completed-order-exchange
#Rabbit MQ concurrect consumers config
spring.rabbitmq.listener.simple.concurrency=3
spring.rabbitmq.listener.simple.retry.initial-interval=3000
Configuration file:
#Configuration
public class RabbitMQConfig {
#Value("${com.cp.neworder.queue.name}")
private String newOrderQueueName;
#Value("${com.cp.neworder.queue.exchange}")
private String newOrderExchangeName;
#Bean
Queue queue() {
return new Queue(newOrderQueueName, true);
}
#Bean
TopicExchange exchange() {
return new TopicExchange(newOrderExchangeName);
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(newOrderQueueName);
}
#Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(newOrderQueueName);
container.setMessageListener(listenerAdapter);
return container;
}
#Bean
MessageListenerAdapter listenerAdapter(OrderMessageListener receiver) {
return new MessageListenerAdapter(receiver, "receiveOrder");
}
}
My consumer class works as intended, it just processes one request at a time. How do I know?
I save the process of my async requests in DB, so I can query how many are processing at the moment, and it's always just 1.
I can look at the RabbitMQ Management platform, and I see that it's being dequeued one by one.
What are the mistakes in my setup? How do I get it to work?
Thanks.
SimpleMessageListenerContainer has a way to set the concurrent consumers. It has setConcurrentConsumers method where you can set the number of consumers.
#Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(newOrderQueueName);
container.setMessageListener(listenerAdapter);
container. setConcurrentConsumers(10);
return container;
}
With this configuration, when you start the application, you will be able to see multiple consumers in the RabbitMQ admin
You are not using Boot to create the container so the boot properties aren't applied.
Try
#Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter,
RabbitProperties properties) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(newOrderQueueName);
container.setMessageListener(listenerAdapter);
container.setConcurrentConsumers(properties.getListener().getSimple().getConcurrency();
return container;
}

Create Shared-Durable subscriber with Spring-boot

I'd like to create a shared-durable subscriber with spring-boot and IBM MQ. I have done to make a durable subscriber, but fail for the shared subscription.
When I debug the program, I found null pointer exception issue
java.lang.NoSuchMethodError: javax.jms.Session.createSharedDurableConsumer(Ljavax/jms/Topic;Ljava/lang/String;Ljava/lang/String;)Ljavax/jms/MessageConsumer;
inside class AbstractMessageListenerContainer, method createConsumer. It is because it try to invoke session.createSharedDurableConsumerMethod where the session object is of javax.jms.Session and IntelliJ point it to library geronimo-jms_1.1_spec-1.1.1.jar
My pom.xml is using JMS 2.0:
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>2.0.1</version>
</dependency>
Here is the java code:
import com.ibm.mq.jms.*;
...
public class JmsConfig {
...
#Bean
public MQTopicConnectionFactory mqTopicConnectionFactory(){
MQTopicConnectionFactory factory = new MQTopicConnectionFactory();
try{
factory.setHostName(mqHostname);
factory.setChannel(mqChannel);
factory.setPort(Integer.parseInt(mqPort));
factory.setQueueManager(mqQManager);
factory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
factory.setClientReconnectTimeout(0);
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
#Bean
#Primary
UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter(MQTopicConnectionFactory mqTopicConnectionFactory) {
UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter = new UserCredentialsConnectionFactoryAdapter();
userCredentialsConnectionFactoryAdapter.setTargetConnectionFactory(mqTopicConnectionFactory);
userCredentialsConnectionFactoryAdapter.setUsername(mqUsername);
if(!mqPassword.equals("")) {
userCredentialsConnectionFactoryAdapter.setPassword(mqPassword);
}
return userCredentialsConnectionFactoryAdapter;
}
#Bean
public CachingConnectionFactory cachingConnectionFactory(UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter) {
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
cachingConnectionFactory.setTargetConnectionFactory(userCredentialsConnectionFactoryAdapter);
cachingConnectionFactory.setSessionCacheSize(1000);
cachingConnectionFactory.setReconnectOnException(true);
return cachingConnectionFactory;
}
#Bean
public JmsOperations jmsOperations(CachingConnectionFactory cachingConnectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
jmsTemplate.setPubSubDomain(true);
jmsTemplate.setReceiveTimeout(2000);
return jmsTemplate;
}
#Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(true);//must follow the configurer.configure(), because it will set the factory as default as connectionFactory
factory.setConcurrency("1");
factory.setSubscriptionShared(true);
factory.setSubscriptionDurable(true);
factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
factory.setClientId(mqClientID);
return factory;
}
}
Is any missing config, or need to override the method to manage the Session object so that it can create shared-durable connection?
Finally I figured out why the program uses JMS1.1. It is because another part of the program load the AWS SQS library from pom, and this library is using JMS1.1(geronimo-jms_1.1_spec-1.1.1.jar) for ActiveMQ.
Therefore, I add exclusion in pom for the AWS SQS library to prevent it override the JMS2.0 library.

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

Atomikos Transaction management spring boot/spring jams

I have a spring boot application with spring JMS using DefaultMessageListener container. I am using Atomikos for transaction management.
On exception the message queue roll back works fine and messages do move to back out queue, but the database updates do not roll back. I have set the autoconfigured JtaTransactionManager on DefaultMessageContainerBean. Are there any other configurations required here to get a true global transaction management. I am using My Batis for database.
public class CusListener implements MessageListener{
public void onMessage(Message message) {
//Database call
catch (Exception ex) {
throw (new RuntimeException());
}
}
}
#Configuration
public class ListenerContainer{
#Bean
public DefaultMessageListenerContainer defaultMessageListenerContainer(ConnectionFactory queueConnectionFactory,MQQueue queue, MessageListener listener,
JtaTransactionManager jtaTransactionManager) {
DefaultMessageListenerContainer defaultMessageListenerContainer =
new DefaultMessageListenerContainer();
defaultMessageListenerContainer.setConnectionFactory(queueConnectionFactory);
defaultMessageListenerContainer.setDestination(queue);
defaultMessageListenerContainer.setMessageListener(listerner);
defaultMessageListenerContainer.setTransactionManager(jtaTransactionManager);
defaultMessageListenerContainer.setSessionTransacted(true);
defaultMessageListenerContainer.setConcurrency("3-10");
return defaultMessageListenerContainer;
}
//other beans declaration passed in the method above
}
#Configuration
public class PlanListenerSqlSessFac {
#Bean(name="sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(#Qualifier("dataSource") NMCryptoDataSourceWrapper dataSource) throws Exception {
}
#Bean(name="driverManagerDataSource")
public DriverManagerDataSource driverManagerDataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
return driverManagerDataSource;
}
}
You should use AtomikosDataSourceBean as dataDource.
See documentation : https://www.atomikos.com/bin/view/Documentation/ConfiguringJdbc

Resources