Saltar al contenido

Cómo crear un contexto JNDI en Spring Boot con un contenedor Tomcat integrado

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

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *