Spring JmsListener for multiple servers - java

I have a web application that connects to multiple jms servers (one at the time) and monitors queues. While this is simple for one jms server I do not know how to configure it for multiple servers. I want user to be able to choose the server at logging in and only then I want JmsListener to monitor queues. User also must be able to change the server while he's already logged in. The problem is how to create and change configuration of these beans at runtime. At the moment my solution is hard-coded like this:
JmsConfig.class
#Bean
public TibjmsConnectionFactory receiverTibjmsConnectionFactory() {
TibjmsConnectionFactory factory=new TibjmsConnectionFactory("myip");
factory.setUserName("username");
factory.setUserPassword("password");
return factory;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory=new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(receiverTibjmsConnectionFactory());
factory.setPubSubDomain(true);
return factory;
}
EmsListener.class
#Component
public class JmsListener {
#org.springframework.jms.annotation.JmsListener(destination="$sys.monitor.Q.r.>")
public void receive(Message message) throws JMSException {
System.out.println("MY MESSAGE IS : "+message.getJMSTimestamp());
}
}

Related

How to understand async vs sync in Spring AMQP?

I'm currently reading through Spring AMQP's official sample project along with it's corresponding explanations from Spring AMQP docs. The project involves an sync and async version, and the two only differs slightly. Here's the async version:
Producer config:
#Configuration
public class ProducerConfiguration {
protected final String helloWorldQueueName = "hello.world.queue";
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(this.helloWorldQueueName);
return template;
}
#Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory();
}
#Bean
public ScheduledProducer scheduledProducer() {
return new ScheduledProducer();
}
#Bean
public BeanPostProcessor postProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
static class ScheduledProducer {
#Autowired
private volatile RabbitTemplate rabbitTemplate;
private final AtomicInteger counter = new AtomicInteger();
#Scheduled(fixedRate = 3000)
public void sendMessage() {
rabbitTemplate.convertAndSend("Hello World " + counter.incrementAndGet());
}
}
}
Consumer config:
#Configuration
public class ConsumerConfiguration {
protected final String helloWorldQueueName = "hello.world.queue";
#Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory();
}
#Bean
public SimpleMessageListenerContainer listenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setQueueNames(this.helloWorldQueueName);
container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler()));
return container;
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(this.helloWorldQueueName);
template.setDefaultReceiveQueue(this.helloWorldQueueName);
return template;
}
#Bean
public Queue helloWorldQueue() {
return new Queue(this.helloWorldQueueName);
}
}
HelloWorldHandler:
public class HelloWorldHandler {
public void handleMessage(String text) {
System.out.println("Received: " + text);
}
}
As the docs explains:
Since this sample demonstrates asynchronous message reception, the producing side is designed to continuously send messages (if it were a message-per-execution model like the synchronous version, it would not be quite so obvious that it is, in fact, a message-driven consumer). The component responsible for continuously sending messages is defined as an inner class within the ProducerConfiguration. It is configured to run every three seconds.
I failed to understand what's "async" about this code, since, from my understanding, in a basic "synchronous fashion", operations like amqpTemplate.converAndSend() and amqpTemplate.receiveAndConvert() already peforms Rabbitmq's async actions, neither producer nor consumer are blocking when sending/receiving messages.
So, how's async in this example manifested? And how to understand async vs sync in the Spring AMQP context?
With async, the MessageListener is invoked by the framework; messages arrive whenever they are available.
With sync, the application calls a receive method which either returns immediately if no message is available, or blocks until a message arrives or a timeout expires.
In the sync case, the application controls when messages are received, with async, the framework is in control.

Dynamic configure ContainerFactory for dynamic JMS Queue with Spring

I'm having to change from simply using #JmsListener to setting up my listeners dynamically in order to allow my user to configure the application choosing which queue to read to.
I have tried follow the Spring JMS Documentation for Programmatic Endpoint Registration but there is one aspect it does not cover: how to set the ListenerContainerFactory I want to use for my listener.
I have tried the following:
#Configuration
#EnableJms
public class JmsConfig implements JmsListenerConfigurer {
#Autowired
private JmsListenerEndpointRegistry registry;
#Overide
public void configureJmsListeners(JmsListenerEndpointRegistrar register) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myJmsEndpoint");
endpoint.setDestination("TestQueue");
endpoint.setupListenerContainer(registry.getListenerContainer("myContainerFactory"))
endpoint.setMessageListener( message -> {
// handle
});
register.registerEndpoint(endpoint)
}
#Bean
public JmsListenerContainerFactory myContainerFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
// Other Connection and Container factories
}
but I'm receiving:
java.lang.IllegalArgumentException: Could not configure endpoint with the specified container 'null' Only JMS (org.springframework.jms.listener.AbstractMessageListenerContainer subclass) or JCA (org.springframework.jms.listener.endpoint.JmsMessageEndpointManager) are supported.
at org.springframework.jms.config.AbstractJmsListenerEndpoint$JcaEndpointConfigurer.configureEndpoint(AbstractJmsListenerEndpoint.java:188)
I'm guessing the null is because, at this phase, Spring still doesn't have the beans created (right?).
What is the right approach here to make this work?
Thanks!

amazon-sqs-java-messaging-lib takes 2 messages at start

I am reading SQS queue with amazon-sqs-java-messaging-lib and #JmsListener.
My configuration is the following:
#Configuration
#EnableJms
public class JmsConfig {
#Bean
public SQSConnectionFactory connectionFactory(AmazonSQS amazonSQS) {
return new SQSConnectionFactory(new ProviderConfiguration(), amazonSQS);
}
#Bean
public JmsTemplate jmsTemplate(SQSConnectionFactory connectionFactory) {
return new JmsTemplate(connectionFactory);
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(SQSConnectionFactory connectionFactory,
MessageConverter converter) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setMaxMessagesPerTask(1);
factory.setConnectionFactory(connectionFactory);
factory.setDestinationResolver(new DynamicDestinationResolver());
factory.setConcurrency("1");
factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
factory.setMessageConverter(converter);
return factory;
}
}
Here I mean that I want to process messages strictly one-by-one.
I'm working on the use-case when my queue contains several messages and the application starts.
When the application connects to the queue it takes 2 messages at the very beginning. Afterwards it consumes one-by-one but the number of messages In Flight is always 2.
(I tried to debug and looks like SQSMessageConsumerPrefetch misses its first reading)
Is it my bad configuration or is it a real issue?
Thank you

spring rabbit and spring transactions

I have a spring rabbit consumer like:
#Override public void onMessage(Message amqpMessage, Channel channel)
throws Exception {
//..some code goes here - I want it to be in spring transaction
}
The issue is the code which is in onMessage method is not under transaction. I checked it, I save data to 3 tables, then throw exception, then save to 4th table. And data from 3 previos tables is not being rolled back. How to do that properly in spring? I want all code in onMessage method to be within a transaction. Thanks
UPDATE
My rabbit conf:
#Configuration #ComponentScan(basePackages = {"com.mycompany"})
public class TicketModeRabbit {
#Bean TicketModeConsumer ticketModeConsumer() {
return new TicketModeConsumer();
}
#Bean(name = TicketModeRabbitData.QUEUE_BEAN_NAME) Queue queue() {
return new Queue(TicketModeRabbitData.QUEUE_BEAN_NAME);
}
#Bean(name = TicketModeRabbitData.QUEUE_BINDING_NAME) Binding binding(
#Qualifier(TicketModeRabbitData.QUEUE_BEAN_NAME) Queue q, TopicExchange e) {
return BindingBuilder.bind(q).to(e).with(TicketModeRabbitData.QUEUE_TOKEN_NAME);
}
#Bean(name = TicketModeRabbitData.CONTAINER_NAME)
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
#Qualifier(TicketModeRabbitData.LISTENER_ADAPTED_NAME)
MessageListenerAdapter listenerAdapter) {
return WorkerConfigHelper
.rabbitConfigListenerContainer(connectionFactory, listenerAdapter,
TicketModeRabbitData.QUEUE_BEAN_NAME,
WorkerConfigHelper.GLOBAL_CONCURRENT_CONSUMERS);
}
#Bean(name = TicketModeRabbitData.LISTENER_ADAPTED_NAME)
MessageListenerAdapter listenerAdapter() {
return new MessageListenerAdapter(ticketModeConsumer());
}
}
If your transaction manager is properly set up for your database, the only thing you need to do is add the #Transactional annotation on the onMessage method. Note that the consumer (MessageListener) needs to be a bean managed by the Spring container.
#Override
#Transactional
public void onMessage(Message amqpMessage, Channel channel)
throws Exception {
//..some code goes here - I want it to be in spring transaction
}

RabbitAdmin - Eager Declaration

I'm using Spring AMQP to set up remoting between different services, as described here. However, as I set a reply-timeout on my configuration, the first ever request always fails because the time taken to declare the queues, exchanges and binding exceeds the timeout:
The RabbitAdmin component can declare exchanges, queues and bindings
on startup. It does this lazily, through a ConnectionListener, so if
the broker is not present on startup it doesn't matter. The first time
a Connection is used (e.g. by sending a message) the listener will
fire and the admin features will be applied.
Is there any way the declaration can be made eagerly on startup instead of being made on the very first publish event to prevent the first request from always failing?
If you declare your queues with annotations:
#Configuration
public class QueuesConfiguration {
#Bean
public FanoutExchange exchange() {
return new FanoutExchange("exchange", true, false);
}
#Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(exchange());
}
#Bean
public Queue queue() {
return new Queue("queue");
}
#Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
}
then call the RabbitAdmin.initialize() manually on application startup with this:
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
#Autowired
private RabbitAdmin rabbitAdmin;
#Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
rabbitAdmin.initialize();
}
}
As we see by that description and the code from the RabbitAdmin, the last one just populates the ConnectionListener to the provided ConnectionFactory.
That ConnectionListener.onCreate is invoked from the ConnectionFactory.createConnection().
So, you can handle, for example, ContextRefreshedEvent and just do void connectionFactory.createConnection() eagerly.
From other side RabbitAdmin has initialize() public method for the same purpose.
UPDATE
Actually ListenerContainer does that on its start() too. You must declare your queues, exchanges and binding in the app where you a have a listener and make it autoStartup = true. To be honest the listener app is responsible for the real Broker entities.
The sending app should get deal just only with exchangeName and routingKey.

Categories