1

Spring Boot 2.3.1.RELEASE.

With spring.jms.cache.enabled=true (default), Spring creates a CachingConnectionFactory:

    @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "true",
            matchIfMissing = true)
    static class CachingConnectionFactoryConfiguration {

This is bad since it shouldn't be used with DefaultMessageListenerContainer. I think it's the reason why some of my messages get "lost" until they suddenly reappear.

With spring.jms.cache.enabled=false, Spring creates an ActiveMQConnectionFactory:

    @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false")
    ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties,
            ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) {
        return createJmsConnectionFactory(properties, factoryCustomizers);
    }

    private static ActiveMQConnectionFactory createJmsConnectionFactory(ActiveMQProperties properties,

This is bad because with each poll, it creates a new connection to the Broker - flooding my broker with hundreds of connections.

So I though the solution to my problems is to use a SingleConnectionFactory. In AbstractPollingMessageListenerContainer.MessageListenerContainerResourceFactory I saw:

    public Connection createConnection() throws JMSException {
        if (AbstractPollingMessageListenerContainer.this.sharedConnectionEnabled()) {
            Connection sharedCon = AbstractPollingMessageListenerContainer.this.getSharedConnection();
            return new SingleConnectionFactory(sharedCon).createConnection();
        }

So I thought I would just:

Jms.channel(connectionFactory)
    .cacheLevel(DefaultMessageListenerContainer.CACHE_CONNECTION)

but as it turns out, this method is never called, only JmsAccessor.createConnection() is which creates an ActiveMQConnectionFactory. My cache level has no effect.

So how do I use SingleConnectionFactory properly?

Michel Jung
  • 2,966
  • 6
  • 31
  • 51

1 Answers1

3

The caching factory is only a problem with the DMLC if you have variable concurrency.

Just define a SingleConnctionFactory as a @Bean and use Jms.channel(mySingleCF())....

EDIT

@SpringBootApplication
public class So63120705Application {

    public static void main(String[] args) {
        SpringApplication.run(So63120705Application.class, args).close(); // JVM should exit
    }

    @Bean
    public ApplicationRunner runner(ConnectionFactory jmsConnectionFactory, IntegrationFlowContext context,
            JmsTemplate template) {

        return args -> {
            SingleConnectionFactory connectionFactory = new SingleConnectionFactory(jmsConnectionFactory);

            IntegrationFlow flow = f -> f.channel(Jms.channel(connectionFactory)
                                .destination("foo"))
                        .handle(System.out::println);

            context.registration(flow)
                .id("jms")
                .addBean(connectionFactory)
                .register();

            template.convertAndSend("foo", "test");
            Thread.sleep(5_000);
        };
    }

}
spring.jms.cache.enabled=false
Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Have just tested and it works as expected. Probably you are confused with the `Jms.channel()` nature. Together with the `DefaultMessageListenerContainer` container where that `CACHE_CONNECTION` is applied, it also has a `JmsTemplate` part which is used to send message into JMS. And this one really creates a fresh connection from the factory on its interaction. – Artem Bilan Jul 27 '20 at 18:19
  • Well, tested with this `Jms.channel(new SingleConnectionFactory(connectionFactory))` and looks like that target `ActiveMQConnectionFactory.createConnection()` is really called only once: `Mockito.verify(this.connectionFactory).createConnection()` – Artem Bilan Jul 27 '20 at 18:27
  • Thank you so much for your support, Gary. I tried to do what you suggested but I'm not sure how to properly `SingleConnectionFactory` as a bean without also having to create my own `ActiveMQConnectionFactory` and do all the property binding myself. Meanwhile I gave `spring.activemq.pool.enable=true` (with `org.messaginghub:pooled-jms`) a go and it worked. I'll give your solution another try once the time pressure is gone :) – Michel Jung Jul 27 '20 at 19:43
  • That is correct; as soon as you define any bean of type `ConnectionFactory`, you are on your own `@ConditionalOnMissingBean(ConnectionFactory.class)`. Unfortunately, while Artem's suggestion works around that problem, it causes another because it's not a `@Bean` and `destroy()` is never called when the context is closed and the JVM doesn't exit. One solution is to register the flow dynamically, which allows you to add it as a bean - see the edit to my answer. – Gary Russell Jul 27 '20 at 20:54