Luego de mucho trabajar pudimos dar con la solución de esta obstáculo que ciertos de nuestros lectores de esta web han presentado. Si deseas compartir algún detalle no dejes de dejar tu conocimiento.
Solución:
De forma predeterminada, JNDI está deshabilitado en Tomcat incrustado, lo que NoInitialContextException
. Necesitas llamar Tomcat.enableNaming()
para habilitarlo. La forma más sencilla de hacerlo es con un TomcatEmbeddedServletContainer
subclase:
@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory()
return new TomcatEmbeddedServletContainerFactory()
@Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat)
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
;
Si adopta este enfoque, también puede registrar el DataSource
en JNDI anulando el postProcessContext
método en tu TomcatEmbeddedServletContainerFactory
subclase.
context.getNamingResources().addResource
agrega el recurso al java:comp/env
contexto, por lo que el nombre del recurso debe ser 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 sea el cargador de clases de contexto de subprocesos. Debería poder lograr esto configurando lookupOnStartup
para false
sobre el jndiObjectFactoryBean
. También necesitará configurar expectedType
para javax.sql.DataSource
:
Esto creará un proxy para el origen de datos 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 este ejemplo de Spring Boot.
Recientemente tuve el requisito de usar JNDI con un Tomcat integrado en Spring Boot.
Las respuestas reales dan algunas pistas interesantes para resolver mi tarea, pero no fue suficiente, ya que probablemente no se actualizó para Spring Boot 2.
Aquí está mi contribución probada con Spring Boot 2.0.3.RELEASE.
Especificar una fuente de datos disponible en classpath en tiempo de ejecución
Tienes múltiples opciones:
- utilizando la fuente de datos DBCP 2 (no desea utilizar DBCP 1 que está desactualizado y es menos eficiente).
- utilizando la fuente de datos Tomcat JDBC.
- utilizando cualquier otra fuente de datos: por ejemplo, HikariCP.
Si no especifica ninguno de ellos, con la configuración predeterminada, la instanciación de la fuente de datos arrojará 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
-
Para usar la fuente de datos Apache JDBC, no necesita agregar ninguna dependencia, pero debe cambiar la clase de fábrica predeterminada a
org.apache.tomcat.jdbc.pool.DataSourceFactory
.
Puedes hacerlo en la declaración de recursos:
resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
Explicaré a continuación dónde agregar esta línea. -
Para utilizar la fuente de datos DBCP 2, se requiere una dependencia:
org.apache.tomcat tomcat-dbcp 8.5.4
Por supuesto, adapte la versión del artefacto de acuerdo con su versión incorporada de Spring Boot Tomcat.
-
Para usar HikariCP, agregue la dependencia requerida si aún no está presente en su configuración (puede estarlo si confía en los iniciadores de persistencia de Spring Boot) como:
com.zaxxer HikariCP 3.1.0
y especifique la fábrica que acompaña a la declaración de recursos:
resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory");
Declaración / configuración de la fuente de datos
Tienes que personalizar el bean que crea el TomcatServletWebServerFactory
ejemplo.
Dos cosas para hacer:
-
Habilitar la denominación JNDI que está deshabilitada de forma predeterminada
-
crear y agregar los recursos JNDI en el contexto del servidor
Por ejemplo, con PostgreSQL y una fuente de datos DBCP 2, haga lo siguiente:
@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);
;
Aquí las variantes para la fuente de datos Tomcat JDBC y HikariCP.
En postProcessContext()
establezca la propiedad de fábrica como se explicó anteriormente para 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);
};
y para HikariCP:
@Override
protected void postProcessContext(Context context)
ContextResource resource = new ContextResource();
//...
resource.setProperty("factory", "com.zaxxer.hikari.HikariDataSource");
//...
context.getNamingResources()
.addResource(resource);
};
Usando / inyectando la fuente de datos
Ahora debería poder buscar el recurso JNDI en cualquier lugar utilizando un estándar InitialContext
instancia:
InitialContext initialContext = new InitialContext();
DataSource datasource = (DataSource) initialContext.lookup("java:comp/env/jdbc/myJndiResource");
También puedes usar JndiObjectFactoryBean
de 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 el DataSource
un frijol de primavera:
@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 ahora puede inyectar el DataSource en cualquier Spring beans como:
@Autowired
private DataSource jndiDataSource;
Tenga en cuenta que muchos ejemplos en Internet parecen deshabilitar la búsqueda del recurso JNDI al 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 afterPropertiesSet()
eso hace la búsqueda!
Después de todo, obtuve 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