example - Cómo crear contexto JNDI en Spring Boot con Embedded Tomcat Container
spring data jndi datasource (5)
¿Has probado @Lazy
cargando el origen de datos? Debido a que está inicializando su contenedor integrado Tomcat dentro del contexto de Spring, debe retrasar la inicialización de su DataSource
(hasta que se hayan configurado los vars JNDI).
NB ¡ No he tenido la oportunidad de probar este código todavía!
@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();
}
También es posible que necesite agregar la anotación @Lazy
siempre que se @Lazy
el DataSource. p.ej
@Lazy
@Autowired
private DataSource dataSource;
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);
}
});
}
}
};
}
}
Estoy usando el arranque de primavera e intento iniciar con un tomcat integrado que crea un contexto JNDI para mis fuentes de datos:
<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 elimino @ImportResource, mi aplicación se iniciará correctamente. Puedo conectarme a la instancia de tomcat. Puedo verificar todos los puntos finales de mi actuador. Utilizando JConsole, puedo conectarme a la aplicación. Puedo ver mi fuente de datos en los MBeans (Catalina -> Recurso -> Contexto -> "/" -> localhost -> javax.sql.DataSource -> jdbc / mydatasource)
También tengo MBeans que aparecen, a través de JConsole, aquí (Tomcat -> DataSource -> / -> localhost -> javax.sql.DataSource -> jdbc / mydatasource)
Sin embargo, cuando yo @ImportResource busca realmente mydatasource a través de JNDI, no lo encuentra.
<bean id="myDS" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/mydatasource"/>
</bean>
La parte relevante de mi archivo xml importado
El ContextResource que configuro arriba está con exactamente los mismos parámetros que estaba usando en el contexto.xml que se implementa cuando la aplicación se implementa en un contenedor de tomcat. Mis beans importados y mi aplicación funcionan correctamente cuando se implementan en un contenedor de tomcat.
Entonces parece que tengo un contexto ahora, pero no parece que el nombre sea correcto. Intenté varias combinaciones del nombre del recurso, pero parece que no puedo generar un límite "comp" en este contexto.
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
De forma predeterminada, JNDI está deshabilitado en Tomcat integrado que causa la NoInitialContextException
. Tomcat.enableNaming()
llamar a Tomcat.enableNaming()
para habilitarlo. La forma más fácil de hacerlo es con una subclase TomcatEmbeddedServletContainer
:
@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
@Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}
Si postProcessContext
este enfoque, también puede registrar DataSource
en JNDI anulando el método postProcessContext
en su subclase TomcatEmbeddedServletContainerFactory
.
context.getNamingResources().addResource
agrega el recurso al contexto java:comp/env
para que el nombre del recurso sea jdbc/mydatasource
no java:comp/env/mydatasource
.
Tomcat utiliza el cargador de clases de contexto de subprocesos para determinar en qué contexto JNDI se debe realizar una búsqueda. Está vinculando el recurso al contexto JNDI de la aplicación web, por lo que debe asegurarse de que la búsqueda se realice cuando el cargador de clases de la aplicación web es el cargador de clases de contexto de subprocesos. Debería poder lograr esto al establecer lookupOnStartup
en false
en jndiObjectFactoryBean
. También deberá establecer expectedType
en 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>
Esto creará un proxy para el DataSource con la búsqueda JNDI real que se realiza en el primer uso en lugar de durante el inicio del contexto de la aplicación.
El enfoque descrito anteriormente se ilustra en esta muestra Spring Boot .
Después de todo, recibí la respuesta gracias a wikisona, primero los frijoles:
@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();
}
el código completo está aquí: https://github.com/wilkinsona/spring-boot-sample-tomcat-jndi
Recientemente tuve el requisito de usar JNDI con un Tomcat integrado en Spring Boot.
Las respuestas reales dan alguna pista para resolver el problema, pero no fueron suficientes, probablemente no se actualizaron.
Aquí está mi contribución probada con Spring Boot 2.0.3.RELEASE.
Pom.xml
Según el origen de datos que haya especificado, es posible que deba proporcionar la biblioteca Tomcat-DBCP.
Por ejemplo, con la configuración predeterminada, la creación de instancias de la fuente de datos lanzó una excepción:
Caused by: javax.naming.NamingException: Could not create resource factory instance at org.apache.naming.factory.ResourceFactory.getDefaultFactory(ResourceFactory.java:50) at org.apache.naming.factory.FactoryBase.getObjectInstance(FactoryBase.java:90) at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:321) at org.apache.naming.NamingContext.lookup(NamingContext.java:839) at org.apache.naming.NamingContext.lookup(NamingContext.java:159) at org.apache.naming.NamingContext.lookup(NamingContext.java:827) at org.apache.naming.NamingContext.lookup(NamingContext.java:159) at org.apache.naming.NamingContext.lookup(NamingContext.java:827) at org.apache.naming.NamingContext.lookup(NamingContext.java:159) at org.apache.naming.NamingContext.lookup(NamingContext.java:827) at org.apache.naming.NamingContext.lookup(NamingContext.java:173) at org.apache.naming.SelectorContext.lookup(SelectorContext.java:163) at javax.naming.InitialContext.lookup(InitialContext.java:417) at org.springframework.jndi.JndiTemplate.lambda$lookup$0(JndiTemplate.java:156) at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:91) at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:156) at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:178) at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:96) at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:114) at org.springframework.jndi.JndiObjectTargetSource.getTarget(JndiObjectTargetSource.java:140) ... 39 common frames omitted Caused by: java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at org.apache.naming.factory.ResourceFactory.getDefaultFactory(ResourceFactory.java:47) ... 58 common frames omitted
Como solución alternativa puede agregar esta dependencia:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.5.4</version>
</dependency>
Por supuesto, adapte la versión del artefacto según su versión de Tomcat.
Configuración de primavera
TomcatServletWebServerFactory
personalizar el bean que crea la instancia de TomcatServletWebServerFactory
.
Dos cosas para hacer:
habilitando el nombre JNDI que está deshabilitado de forma predeterminada
crear y agregar los recursos JNDI en el contexto del servidor
Por ejemplo, con un origen de datos de base de datos PostgreSQL se ve así:
@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);
}
};
}
Ahora debería poder buscar el recurso JNDI en cualquier lugar utilizando una instancia estándar de InitialContext
:
InitialContext initialContext = new InitialContext();
DataSource datasource = (DataSource) initialContext.lookup("java:comp/env/jdbc/myJndiResource");
También puede usar JndiObjectFactoryBean
of Spring para buscar el recurso:
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:comp/env/jdbc/myJndiResource");
bean.afterPropertiesSet();
DataSource object = (DataSource) bean.getObject();
Para aprovechar el contenedor DI también puede hacer que DataSource
un bean Spring:
@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();
}
Y entonces ahora puede inyectar DataSource en cualquier grano de Spring como por ejemplo:
@Autowired
private DataSource jndiDataSource;
Tenga en cuenta que muchos ejemplos en Internet parecen desactivar la búsqueda del recurso JNDI en el inicio:
bean.setJndiName("java:comp/env/jdbc/myJndiResource");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
Pero creo que es impotente, ya que invoca justo después de afterPropertiesSet()
que hace la búsqueda!
Tenga en cuenta en lugar de
public TomcatEmbeddedServletContainerFactory tomcatFactory()
Tuve que usar la siguiente firma de método
public EmbeddedServletContainerFactory embeddedServletContainerFactory()