首页 > 解决方案 > Retrive JMS ConnectionFactory from JNDI using Spring Boot auto-configuration

问题描述

I want to use the spring boot autoconfigure for JMS to connect to a remote JNDI and retrieve the ConnectionFactory based on his name populated through the spring.jms.jndi-name property in the application.properties file.

I noticed that the spring boot autoconfigure is relying on the JndiConnectionFactoryAutoConfiguration class to do that and this class in turn will call the JndiTemplate class to do the lookup. The problem is that the value of the environment attribute of the JndiTemplate class is null, so we cannot create the intialContext.

In fact, I noticed that the JndiTemplate class is instantiated with no-argument constructor in starting application and before loading the configuration defined in the JndiConnectionFactoryAutoConfiguration class.

My question: how can I instantiate JndiTemplate by specifying a list of properties (Context.INITIAL_CONTEXT_FACTORY, Context.PROVIDER_URL..)? knowing that JmsTemplate has a constructor that takes an Properties object.

Just for information: my application is a simple jar that doesn’t run on a server at the moment.

标签: springspring-bootjmsjndispring-jms

解决方案


For those interested in the answer, you must use VM options to pass required JNDI properties.

Here is an example that works with ActiveMQ:

VM options:

-Djava.naming.provider.url=tcp://hostname:61616

-Djava.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory

And spring properties file (application.properties) must contain the JNDI name of the connection factory:

spring.jms.jndi-name=ConnectionFactory

Much better, you can use configuration to fin your connection factory from JNDI. In my project, we finished by creating our jms starter that we can use in all microservices.

Properties class:

import lombok.*;
import org.springframework.boot.context.properties.ConfigurationProperties;


@Getter
@Setter
@ToString
@NoArgsConstructor
@EqualsAndHashCode
@ConfigurationProperties( prefix = "custom.jms" )
public class CustomJmsProperties {
    private String jndiName;
    private String contextFactoryClass;
    private String providerUrl;
    private String username;
    private String password;
}

Configuration class:

import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter;
import org.springframework.jndi.JndiLocatorDelegate;

import javax.jms.ConnectionFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import java.util.Properties;

@Configuration
@ConditionalOnProperty( "custom.jms.jndi-name" )
@ConditionalOnMissingBean( ConnectionFactory.class )
@EnableConfigurationProperties( { CustomJmsProperties.class } )
@AutoConfigureAfter( { JndiConnectionFactoryAutoConfiguration.class } )
public class CustomJndiConnectionFactoryAutoConfiguration {

    @Bean
    public ConnectionFactory connectionFactory( CustomJmsProperties customJmsProperties ) throws NamingException {
        ConnectionFactory connectionFactory = lookupForConnectionFactory( customJmsProperties );
        return getEnhancedUserCredentialsConnectionFactory( customJmsProperties, connectionFactory );
    }

    private ConnectionFactory lookupForConnectionFactory( final CustomJmsProperties customJmsProperties ) throws NamingException {
        JndiLocatorDelegate jndiLocatorDelegate = new JndiLocatorDelegate();
        Properties jndiProperties = getJndiProperties( customJmsProperties );
        jndiLocatorDelegate.setJndiEnvironment( jndiProperties );
        return jndiLocatorDelegate.lookup( customJmsProperties.getJndiName(), ConnectionFactory.class );
    }

    private Properties getJndiProperties( final CustomJmsProperties customJmsProperties ) {
        Properties jndiProperties = new Properties();
        jndiProperties.setProperty( Context.PROVIDER_URL, customJmsProperties.getProviderUrl() );
        jndiProperties.setProperty( Context.INITIAL_CONTEXT_FACTORY, customJmsProperties.getContextFactoryClass() );
        if ( StringUtils.isNotEmpty( customJmsProperties.getUsername() ) ) {
            jndiProperties.setProperty( Context.SECURITY_PRINCIPAL, customJmsProperties.getUsername() );
        }
        if ( StringUtils.isNotEmpty( customJmsProperties.getPassword() ) ) {
            jndiProperties.setProperty( Context.SECURITY_CREDENTIALS, customJmsProperties.getPassword() );
        }
        return jndiProperties;
    }

    private UserCredentialsConnectionFactoryAdapter getEnhancedUserCredentialsConnectionFactory( final CustomJmsProperties customJmsProperties,
                                                                                                 final ConnectionFactory connectionFactory ) {
        UserCredentialsConnectionFactoryAdapter enhancedConnectionFactory = new UserCredentialsConnectionFactoryAdapter();
        enhancedConnectionFactory.setTargetConnectionFactory( connectionFactory );
        enhancedConnectionFactory.setUsername( customJmsProperties.getUsername() );
        enhancedConnectionFactory.setPassword( customJmsProperties.getPassword() );
        enhancedConnectionFactory.afterPropertiesSet();
        return enhancedConnectionFactory;
    }
}

Properties file of your project:

custom.jms.provider-url=tcp://hostname:61616
custom.jms.context-factory-class=org.apache.activemq.jndi.ActiveMQInitialContextFactory
custom.jms.jndi-name=ConnectionFactory

推荐阅读