web-dev-qa-db-fra.com

Comment créer un contexte JNDI dans Spring Boot avec un conteneur Tomcat intégré

import org.Apache.catalina.Context;
import org.Apache.catalina.deploy.ContextResource;
import org.Apache.catalina.startup.Tomcat;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.Tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.Tomcat.TomcatEmbeddedServletContainer;
import org.springframework.boot.context.embedded.Tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@EnableAutoConfiguration
@ComponentScan
@ImportResource("classpath:applicationContext.xml")
public class Application {

    public static void main(String[] args) throws Exception {
        new SpringApplicationBuilder()
                .showBanner(false)
                .sources(Application.class)
                .run(args);
}

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {
        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            Tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(Tomcat);
        }
    };
}

@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
    return new EmbeddedServletContainerCustomizer() {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = (TomcatEmbeddedServletContainerFactory) container;
                tomcatEmbeddedServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer() {
                    @Override
                    public void customize(Context context) {
                        ContextResource mydatasource = new ContextResource();
                        mydatasource.setName("jdbc/mydatasource");
                        mydatasource.setAuth("Container");
                        mydatasource.setType("javax.sql.DataSource");
                        mydatasource.setScope("Sharable");
                        mydatasource.setProperty("driverClassName", "Oracle.jdbc.driver.OracleDriver");
                        mydatasource.setProperty("url", "jdbc:Oracle:thin:@mydomain.com:1522:myid");
                        mydatasource.setProperty("username", "myusername");
                        mydatasource.setProperty("password", "mypassword");

                        context.getNamingResources().addResource(mydatasource);

                    }
                });
            }
        }
    };
}

}

J'utilise Spring Boot et j'essaie de démarrer avec un Tomcat intégré qui crée un contexte JNDI pour mes sources de données:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        <version>1.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-Tomcat</artifactId>
        <version>1.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-Oracle</artifactId>
        <version>1.0.0.RELEASE</version>
    </dependency>

Si je supprime @ImportResource, mon application démarre parfaitement. Je peux me connecter à l'instance Tomcat. Je peux vérifier tous mes points d'extrémité d'actionneur. En utilisant JConsole, je peux me connecter à l'application et voir ma source de données dans les MBeans (Catalina -> Ressource -> Contexte -> "/" -> localhost -> javax.sql.DataSource -> jdbc/mydatasource)

J'ai aussi des MBeans qui apparaissent, via JConsole, ici (Tomcat -> DataSource ->/-> localhost -> javax.sql.DataSource -> jdbc/mydatasource)

Cependant, lorsque je @ImportResource recherche ce que recherche réellement mydatasource via JNDI, il ne le trouve pas.

<bean id="myDS" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="Java:comp/env/jdbc/mydatasource"/>
</bean>

La partie pertinente de mon fichier XML importé

ContextResource que je configure ci-dessus utilise exactement les mêmes paramètres que ceux que j'utilisais dans le fichier context.xml qui est déployé lorsque l'application est déployée dans un conteneur Tomcat. Mes beans importés et mon application fonctionnent correctement lorsqu'ils sont déployés dans un conteneur Tomcat.

Donc, il semble que j'ai un contexte maintenant, mais il ne semble pas que le nom soit correct. J'ai essayé différentes combinaisons du nom de la ressource, mais n'arrive pas à générer une "comp" liée dans ce contexte.

Caused by: javax.naming.NameNotFoundException: Name [comp/env/jdbc/mydatasource] is not bound in this Context. Unable to find [comp].
    at org.Apache.naming.NamingContext.lookup(NamingContext.Java:819)
    at org.Apache.naming.NamingContext.lookup(NamingContext.Java:167)
    at org.Apache.naming.SelectorContext.lookup(SelectorContext.Java:156)
    at javax.naming.InitialContext.lookup(InitialContext.Java:392)
    at org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.Java:155)
    at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.Java:87)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.Java:152)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.Java:179)
    at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.Java:95)
    at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.Java:106)
    at org.springframework.jndi.JndiObjectFactoryBean.lookupWithFallback(JndiObjectFactoryBean.Java:231)
    at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.Java:217)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.Java:1612)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.Java:1549)
    ... 30 more
40
DaShaun

Par défaut, JNDI est désactivé dans Tomcat intégré, ce qui cause la variable NoInitialContextException. Vous devez appeler Tomcat.enableNaming() pour l'activer. Le moyen le plus simple de procéder consiste à utiliser une sous-classe TomcatEmbeddedServletContainer:

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            Tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(Tomcat);
        }
    };
}

Si vous utilisez cette approche, vous pouvez également enregistrer la variable DataSource dans JNDI en remplaçant la méthode postProcessContext dans votre sous-classe TomcatEmbeddedServletContainerFactory.

context.getNamingResources().addResource ajoute la ressource au contexte Java:comp/env de sorte que le nom de la ressource devrait être jdbc/mydatasource et non Java:comp/env/mydatasource.

Tomcat utilise le chargeur de classes de contexte de thread pour déterminer le contexte JNDI avec lequel une recherche doit être effectuée. Vous liez la ressource au contexte JNDI de l'application Web. Vous devez donc vous assurer que la recherche est effectuée lorsque le chargeur de classes de l'application Web est le chargeur de classes de contexte de thread. Vous devriez pouvoir y parvenir en définissant lookupOnStartup à false sur jndiObjectFactoryBean. Vous devrez également définir expectedType sur javax.sql.DataSource:

<bean class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="Java:comp/env/jdbc/mydatasource"/>
    <property name="expectedType" value="javax.sql.DataSource"/>
    <property name="lookupOnStartup" value="false"/>
</bean>

Cela créera un proxy pour le DataSource avec la recherche JNDI réelle exécutée lors de la première utilisation plutôt que lors du démarrage du contexte d'application.

L'approche décrite ci-dessus est illustrée dans cet exemple Spring Boot .

53
Andy Wilkinson

Après tout, j’ai eu la réponse grâce à wikisona, d’abord les haricots:

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            Tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(Tomcat);
        }

        @Override
        protected void postProcessContext(Context context) {
            ContextResource resource = new ContextResource();
            resource.setName("jdbc/myDataSource");
            resource.setType(DataSource.class.getName());
            resource.setProperty("driverClassName", "your.db.Driver");
            resource.setProperty("url", "jdbc:yourDb");

            context.getNamingResources().addResource(resource);
        }
    };
}

@Bean(destroyMethod="")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
    JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
    bean.setJndiName("Java:comp/env/jdbc/myDataSource");
    bean.setProxyInterface(DataSource.class);
    bean.setLookupOnStartup(false);
    bean.afterPropertiesSet();
    return (DataSource)bean.getObject();
}

le code complet c'est ici: https://github.com/wilkinsona/spring-boot-sample-Tomcat-jndi

11
nekperu15739

J'avais récemment l'obligation d'utiliser JNDI avec un Tomcat intégré dans Spring Boot.
Les réponses réelles donnent quelques astuces intéressantes pour résoudre ma tâche, mais ce n’était pas suffisant, car il n’a probablement pas été mis à jour pour Spring Boot 2. 

Voici ma contribution testée avec Spring Boot 2.0.3.RELEASE. 

Spécification d'une source de données disponible dans le chemin d'accès aux classes à l'exécution  

Vous avez plusieurs choix: 

  • en utilisant la source de données DBCP 2 (vous ne voulez pas utiliser DBCP 1 qui est obsolète et moins efficace).
  • en utilisant la source de données Tomcat JDBC. 
  • en utilisant n'importe quelle autre source de données: par exemple HikariCP. 

Si vous n'en spécifiez aucune, avec la configuration par défaut, l'instanciation de la source de données lève une exception:

 Causée par: javax.naming.NamingException: impossible de créer une instance de fabrique de ressources 
 à org.Apache.naming.factory.ResourceFactory.getDefaultFactory (ResourceFactory.Java:50) 
 sur org.Apache.naming.factory.FactoryBase.getObjectInstance (FactoryBase.Java:90) 
 à l'adresse javax.naming.spi.NamingManager.getObjectInstance (NamingManager.Java:321) 
 à org.Apache.naming.NamingContext.lookup (NamingContext.Java:839) 
 à org.Apache.naming.NamingContext.lookup (NamingContext.Java:159) 
 à org.Apache.naming.NamingContext.lookup (NamingContext.Java:827) 
 à org.Apache.naming.NamingContext.lookup (NamingContext.Java:159) 
 à org.Apache.naming.NamingContext.lookup (NamingContext.Java:827) 
 à org.Apache.naming.NamingContext.lookup (NamingContext.Java:159) 
 à org.Apache.naming.NamingContext.lookup (NamingContext.Java:827) 
 à org.Apache.naming.NamingContext.lookup (NamingContext.Java:173) 
 à org.Apache.naming.SelectorContext.lookup (SelectorContext.Java:163) 
 à javax.naming.InitialContext.lookup (InitialContext.Java:417) 
 à org.springframework.jndi.JndiTemplate.lambda $ lookup $ 0 (JndiTemplate.Java:156) 
 à org.springframework.jndi.JndiTemplate.execute (JndiTemplate.Java:91) 
 à org.springframework.jndi.JndiTemplate.lookup (JndiTemplate.Java:156) 
 à org.springframework.jndi.JndiTemplate.lookup (JndiTemplate.Java:178) 
 à org.springframework.jndi.JndiLocatorSupport.lookup (JndiLocatorSupport.Java:96) 
 à org.springframework.jndi.JndiObjectLocator.lookup (JndiObjectLocator.Java:114) 
 à org.springframework.jndi.JndiObjectTargetSource.getTarget (JndiObjectTargetSource.Java:140) 
 ... 39 cadres communs omis 
 Causée par: Java.lang.ClassNotFoundException: org.Apache.Tomcat.dbcp.dbcp2.BasicDataSourceFactory 
 sur Java.net.URLClassLoader.findClass (URLClassLoader.Java:381) 
 sur Java.lang.ClassLoader.loadClass (ClassLoader.Java:424) 
 à Sun.misc.Launcher $ AppClassLoader.loadClass (Launcher.Java:331) 
 sur Java.lang.ClassLoader.loadClass (ClassLoader.Java:357) 
 sur Java.lang.Class.forName0 (méthode native) 
 sur Java.lang.Class.forName (Class.Java:264) 
 at org.Apache.naming.factory.ResourceFactory.getDefaultFactory (ResourceFactory.Java:47) 
 ... 58 cadres communs omis 

  • Pour utiliser la source de données Apache JDBC, vous n'avez pas besoin d'ajouter de dépendance, mais vous devez modifier la classe de fabrique par défaut en org.Apache.Tomcat.jdbc.pool.DataSourceFactory.
    Vous pouvez le faire dans la déclaration de ressource: resource.setProperty("factory", "org.Apache.Tomcat.jdbc.pool.DataSourceFactory"); Je vais expliquer ci-dessous où ajouter cette ligne. 

  • Pour utiliser la source de données DBCP 2, une dépendance est requise:

    <dependency> <groupId>org.Apache.Tomcat</groupId> <artifactId>Tomcat-dbcp</artifactId> <version>8.5.4</version> </dependency>

Bien sûr, adaptez la version de l'artefact à votre version intégrée de Spring Boot Tomcat. 

  • Pour utiliser HikariCP, ajoutez la dépendance requise si elle n’est pas déjà présente dans votre configuration (peut-être si vous comptez sur des démarreurs de persistance de Spring Boot), telles que: 

    <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.1.0</version> </dependency>

et spécifiez l'usine qui va avec dans la déclaration de ressource: 

resource.setProperty("factory", "com.zaxxer.hikari.HikariDataSource");

Configuration de la source de données/déclaration  

Vous devez personnaliser le bean qui crée l'instance TomcatServletWebServerFactory.
Deux choses à faire:

  • l'activation de la dénomination JNDI qui est désactivée par défaut

  • créer et ajouter les ressources JNDI dans le contexte du serveur

Par exemple, avec PostgreSQL et une source de données DBCP 2, procédez comme suit:

@Bean
public TomcatServletWebServerFactory tomcatFactory() {
    return new TomcatServletWebServerFactory() {
        @Override
        protected TomcatWebServer getTomcatWebServer(org.Apache.catalina.startup.Tomcat tomcat) {
            Tomcat.enableNaming(); 
            return super.getTomcatWebServer(Tomcat);
        }

        @Override 
        protected void postProcessContext(Context context) {

            // context
            ContextResource resource = new ContextResource();
            resource.setName("jdbc/myJndiResource");
            resource.setType(DataSource.class.getName());
            resource.setProperty("driverClassName", "org.postgresql.Driver");

            resource.setProperty("url", "jdbc:postgresql://hostname:port/dbname");
            resource.setProperty("username", "username");
            resource.setProperty("password", "password");
            context.getNamingResources()
                   .addResource(resource);          
        }
    };
}

Voici les variantes pour la source de données Tomcat JDBC et HikariCP. 

Dans postProcessContext(), définissez la propriété factory comme expliqué précédemment pour Tomcat JDBC ds: 

    @Override 
    protected void postProcessContext(Context context) {
        ContextResource resource = new ContextResource();       
        //...
        resource.setProperty("factory", "org.Apache.Tomcat.jdbc.pool.DataSourceFactory");
        //...
        context.getNamingResources()
               .addResource(resource);          
    }
};

et pour HikariCP:

    @Override 
    protected void postProcessContext(Context context) {
        ContextResource resource = new ContextResource();       
        //...
        resource.setProperty("factory", "com.zaxxer.hikari.HikariDataSource");
        //...
        context.getNamingResources()
               .addResource(resource);          
    }
};

Utilisation/Injection de la source de données  

Vous devriez maintenant pouvoir consulter la ressource JNDI n’importe où en utilisant une instance InitialContext standard:

InitialContext initialContext = new InitialContext();
DataSource datasource = (DataSource) initialContext.lookup("Java:comp/env/jdbc/myJndiResource");

Vous pouvez également utiliser JndiObjectFactoryBean of Spring pour rechercher la ressource:

JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("Java:comp/env/jdbc/myJndiResource");
bean.afterPropertiesSet();
DataSource object = (DataSource) bean.getObject();

Pour tirer parti du conteneur DI, vous pouvez également transformer la DataSource en haricot de printemps:

@Bean(destroyMethod = "")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
    JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
    bean.setJndiName("Java:comp/env/jdbc/myJndiResource");
    bean.afterPropertiesSet();
    return (DataSource) bean.getObject();
}

Et ainsi, vous pouvez maintenant injecter le DataSource dans n’importe quel bean Spring, tel que:

@Autowired
private DataSource jndiDataSource;

Notez que de nombreux exemples sur Internet semblent désactiver la recherche de la ressource JNDI au démarrage:

bean.setJndiName("Java:comp/env/jdbc/myJndiResource");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet(); 

Mais je pense que c'est impuissant car il invoque juste après afterPropertiesSet() que fait la recherche! 

6
davidxxx

Veuillez noter au lieu de 

public TomcatEmbeddedServletContainerFactory tomcatFactory()

Je devais utiliser la signature de méthode suivante

public EmbeddedServletContainerFactory embeddedServletContainerFactory() 
1
jerome

Avez-vous essayé @Lazy en chargeant la source de données? Comme vous initialisez votre conteneur Tomcat intégré dans le contexte Spring, vous devez différer l’initialisation de votre DataSource (jusqu’à ce que les vars JNDI aient été configurés).

N.B. Je n'ai pas encore eu l'occasion de tester ce code!

@Lazy
@Bean(destroyMethod="")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
    JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
    bean.setJndiName("Java:comp/env/jdbc/myDataSource");
    bean.setProxyInterface(DataSource.class);
    //bean.setLookupOnStartup(false);
    bean.afterPropertiesSet();
    return (DataSource)bean.getObject();
}

Vous devrez peut-être aussi ajouter l'annotation @Lazy à chaque fois que la source de données est utilisée. par exemple.

@Lazy
@Autowired
private DataSource dataSource;
0
Nick Grealy

Au printemps de démarrage 2.1, j'ai trouvé une autre solution . Étendre la méthode de classe d'usine standard getTomcatWebServer. Et puis le retourner comme un haricot de n'importe où.

public class CustomTomcatServletWebServerFactory extends TomcatServletWebServerFactory {

    @Override
    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        System.setProperty("catalina.useNaming", "true");
        Tomcat.enableNaming();
        return new TomcatWebServer(Tomcat, getPort() >= 0);
    }
}




@Component
public class TomcatConfiguration {
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        TomcatServletWebServerFactory factory = new CustomTomcatServletWebServerFactory();

        return factory;
    }

Le chargement de ressources depuis context.xml ne fonctionne pas. Je vais essayer de savoir.

0
gotozero