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
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 .
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
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:
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!
Veuillez noter au lieu de
public TomcatEmbeddedServletContainerFactory tomcatFactory()
Je devais utiliser la signature de méthode suivante
public EmbeddedServletContainerFactory embeddedServletContainerFactory()
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;
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.