0

I create a message handler this way:

@JmsListeners(
  JmsListener(destination = "queue1"),
  JmsListener(destination = "queue2"),
  JmsListener(destination = "queue3"),
  JmsListener(destination = "queue4")
)
fun handleMessage(message: String) {
  // handle a message
}

When I check my message broker, I see that my app has established 4 connections. Unfortunately, I have limitations on amount of connections from MQ admins, so I would like message handler to use only 1 connection.

After checking Spring Jms internals, I found out that DefaultMessageListenerContainer has an ability to use a shared connection. But the problem is that Spring's DefaultMessageListenerContainerFactory creates a separate DefaultMessageListenerContainer for each @JmsListener.

At the same time, JMS API allows creating multiple JMSConsumers from a single JMSContext, e.g.

val jmsContext = connectionFactory.createContext(Session.SESSION_TRANSACTED)
val consumer1 = jmsContext.createConsumer(jmsContext.createQueue("queue1"))
val consumer2 = jmsContext.createConsumer(jmsContext.createQueue("queue2"))

How can I set up JmsListeners to share a common connection? And if this isn't possible, does Spring has a sensible reason for it?

  • 1
    What do you think a [`SingleConnectionFactory`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jms/connection/SingleConnectionFactory.html) does... – M. Deinum Jan 17 '21 at 08:27
  • What version of MQ jar files? What is `SHARECNV` set to on the queue manager `SVRCONN` channel? – JoshMc Jan 17 '21 at 09:37
  • 1
    SHARECNV doesn't change the number of connections, only the number of TCP sockets used for those connections. But you also have to remember that a JMS Connection is not the same as an MQ Connection - simplistically, each JMS Connection and each JMS Session all correspond to a separate MQ Connection. So a typical JMS app (1 connection + 1 session) will use 2 MQ Connections.And you then have to take that into account when sizing a qmgr. – Mark Taylor Jan 18 '21 at 06:44
  • I wrap `MQConnectionFactory` in a `JmsPoolConnectionFactory` with `maxConnections == 1`. So there's only 1 instance of `MQConnection`. In a debugger I also checked that `MQConnectionFactory.createConnection()` and `MQConnection.createSession()` invoked once each one. – Kirill Fertikov Jan 18 '21 at 11:10

1 Answers1

0

I wanted to post this as a comment, but I also need to show code, so as a compromise ...

I think you have done this already. This is is like peeling an onion.

It doesn't look as though it is possible control the sharedConnection through application.properties - https://docs.spring.io/spring-boot/docs/2.4.1/reference/htmlsingle/#common-application-properties-integration

Nor, can it be set directly in code, but there might be a way to influence it. Using one or more of the mechanisms below.

For the listener you are going to have to specify the container factory. BTW. I don't like hard coded strings, so in application.properties there is a my.queue.name1 property.

import org.springframework.jms.annotation.JmsListener;
    ...

    // Set upper concurrency limit on listener
    @JmsListener(destination = "${my.queue.name1}", containerFactory = "myListenerFactory", concurrency = "2")
    ...

Peeling back, you will need a bean for the customised factory, where you can control how the connections get created. It's possible to @Override the createContainerInstance method, but the DefaultMessageListenerContainer::sharedConnectionEnabled method is protected, and final, so not possible to invoke nor override. So you need to use other ways to limit the number of connections available ...

import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
...    

    @Bean("myListenerFactory")
    public DefaultJmsListenerContainerFactory myCustomisedListenerFactory() {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory() {
            @Override
            protected DefaultMessageListenerContainer createContainerInstance() {
                DefaultMessageListenerContainer dmlc = new DefaultMessageListenerContainer();

                // This may the way to control how many concurrent consumers are created
                dmlc.setMaxConcurrentConsumers(2);
                return dmlc;
            }
        };
        factory.setConnectionFactory(connectionFactory);

        // Set the concurrency lower limit to upper limit
        factory.setConcurrency("1-2");

        // Plus any other customisations
        ...

        return factory;
    }

and your auto wired connection factory.

import import javax.jms.ConnectionFactory;
...

    @Autowired
    private ConnectionFactory connectionFactory;
chughts
  • 4,210
  • 2
  • 14
  • 27