Como criar um contexto JNDI no Spring Boot com o contêiner do Tomcat incorporado

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); } }); } } }; } 

}

Estou usando o spring boot e tentando inicializar com um tomcat incorporado que cria um contexto JNDI para minhas origens de dados:

   org.springframework.boot spring-boot-starter-actuator 1.1.4.RELEASE   org.springframework.boot spring-boot-starter-tomcat 1.1.4.RELEASE   org.springframework.data spring-data-oracle 1.0.0.RELEASE  

Se eu remover o @ImportResource, meu aplicativo é iniciado muito bem. Eu posso conectar-me à instância do tomcat. Posso verificar todos os endpoints do meu atuador. Usando o JConsole, posso conectar-me ao aplicativo. Posso ver minha fonte de dados nos MBeans (Catalina -> Recurso -> Contexto -> “/” -> localhost -> javax.sql.DataSource -> jdbc / mydatasource)

Eu também tenho MBeans aparecendo, via JConsole, aqui (Tomcat -> DataSource -> / -> localhost -> javax.sql.DataSource -> jdbc / mydatasource)

No entanto, quando eu @ImportResource o que realmente está procurando mydatasource via JNDI, não é encontrá-lo.

    

A parte relevante do meu arquivo xml importado

O ContextResource que estou configurando acima está com exatamente os mesmos parâmetros que eu estava usando no context.xml que está sendo implantado quando o aplicativo é implantado em um contêiner do tomcat. Meus beans importados e meu aplicativo estão funcionando corretamente quando implantados em um contêiner do tomcat.

Então, parece que eu tenho um contexto agora, mas não parece que a nomeação esteja correta. Eu tentei várias combinações do nome do recurso, mas não consigo gerar um “comp” ligado neste 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 

Por padrão, o JNDI está desativado no Tomcat incorporado, que está causando o NoInitialContextException . Você precisa chamar Tomcat.enableNaming() para ativá-lo. A maneira mais fácil de fazer isso é com uma subclass TomcatEmbeddedServletContainer :

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

Se você postProcessContext essa abordagem, também poderá registrar o DataSource no JNDI substituindo o método postProcessContext na sua subclass TomcatEmbeddedServletContainerFactory .

context.getNamingResources().addResource adiciona o recurso ao contexto java:comp/env portanto, o nome do recurso deve ser jdbc/mydatasource não java:comp/env/mydatasource .

O Tomcat usa o carregador de classs de contexto de encadeamento para determinar com qual contexto JNDI uma pesquisa deve ser executada. Você está vinculando o recurso ao contexto JNDI do aplicativo Web, portanto, é necessário garantir que a consulta seja executada quando o carregador de classs do aplicativo da Web for o carregador de classs de contexto do encadeamento. Você deve conseguir isso configurando lookupOnStartup como false no jndiObjectFactoryBean . Você também precisará definir o expectedType como javax.sql.DataSource :

      

Isso criará um proxy para o DataSource com a consulta real do JNDI sendo executada no primeiro uso, e não durante a boot do contexto do aplicativo.

A abordagem descrita acima é ilustrada neste exemplo de boot de Spring .

Afinal eu tenho a resposta graças a wikisona, primeiro os feijões:

 @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(); } 

o código completo está aqui: https://github.com/wilkinsona/spring-boot-sample-tomcat-jndi

Por favor, note em vez de

 public TomcatEmbeddedServletContainerFactory tomcatFactory() 

Eu tive que usar a seguinte assinatura de método

 public EmbeddedServletContainerFactory embeddedServletContainerFactory() 

Você já tentou @Lazy carregar a fonte de dados? Como você está inicializando o contêiner do Tomcat incorporado no contexto do Spring, é necessário atrasar a boot do seu DataSource (até que os JNDI vars tenham sido configurados).

NB eu não tive a chance de testar este código ainda!

 @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(); } 

Você também pode precisar adicionar a anotação @Lazy sempre que o DataSource estiver sendo usado. por exemplo

 @Lazy @Autowired private DataSource dataSource; 

Recentemente, tive o requisito de usar o JNDI com um Tomcat incorporado no Spring Boot.
Respostas reais dão alguma dica para resolver o problema, mas não foi suficiente, provavelmente, não atualizado.

Aqui está minha contribuição testada com o Spring Boot 2.0.3.RELEASE.

Pom.xml

De acordo com a fonte de dados que você especificou, você pode precisar fornecer a biblioteca Tomcat-DBCP.
Por exemplo, com a configuração padrão, a instanciação da fonte de dados lançou uma exceção:

 Causado por: javax.naming.NamingException: não foi possível criar a instância do resource factory
         em org.apache.naming.factory.ResourceFactory.getDefaultFactory (ResourceFactory.java:50)
         em org.apache.naming.factory.FactoryBase.getObjectInstance (FactoryBase.java:90)
         em javax.naming.spi.NamingManager.getObjectInstance (NamingManager.java:321)
         em org.apache.naming.NamingContext.lookup (NamingContext.java:839)
         em org.apache.naming.NamingContext.lookup (NamingContext.java:159)
         em org.apache.naming.NamingContext.lookup (NamingContext.java:827)
         em org.apache.naming.NamingContext.lookup (NamingContext.java:159)
         em org.apache.naming.NamingContext.lookup (NamingContext.java:827)
         em org.apache.naming.NamingContext.lookup (NamingContext.java:159)
         em org.apache.naming.NamingContext.lookup (NamingContext.java:827)
         em org.apache.naming.NamingContext.lookup (NamingContext.java:173)
         em org.apache.naming.SelectorContext.lookup (SelectorContext.java:163)
         em javax.naming.InitialContext.lookup (InitialContext.java:417)
         em org.springframework.jndi.JndiTemplate.lambda $ lookup $ 0 (JndiTemplate.java:156)
         em org.springframework.jndi.JndiTemplate.execute (JndiTemplate.java:91)
         em org.springframework.jndi.JndiTemplate.lookup (JndiTemplate.java:156)
         em org.springframework.jndi.JndiTemplate.lookup (JndiTemplate.java:178)
         em org.springframework.jndi.JndiLocatorSupport.lookup (JndiLocatorSupport.java:96)
         em org.springframework.jndi.JndiObjectLocator.lookup (JndiObjectLocator.java:114)
         em org.springframework.jndi.JndiObjectTargetSource.getTarget (JndiObjectTargetSource.java:140)
         ... 39 frameworks comuns omitidos
 Causado por: java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory
         em java.net.URLClassLoader.findClass (URLClassLoader.java:381)
         em java.lang.ClassLoader.loadClass (ClassLoader.java:424)
         em sun.misc.Launcher $ AppClassLoader.loadClass (Launcher.java:331)
         em java.lang.ClassLoader.loadClass (ClassLoader.java:357)
         em java.lang.Class.forName0 (método nativo)
         em java.lang.Class.forName (Class.java:264)
         em org.apache.naming.factory.ResourceFactory.getDefaultFactory (ResourceFactory.java:47)
         ... 58 frameworks comuns omitidos

Como solução alternativa, você pode adicionar essa dependência:

  org.apache.tomcat tomcat-dbcp 8.5.4  

Claro, adapte a versão do artefato de acordo com a sua versão do Tomcat.

Configuração de spring

Você precisa customizar o bean que cria a instância TomcatServletWebServerFactory .
Duas coisas para fazer:

  • ativando a nomenclatura JNDI que está desativada por padrão

  • criando e adicionando o (s) recurso (s) JNDI no contexto do servidor

Por exemplo, com uma fonte de dados de database PostgreSQL, parece:

 @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); } }; } 

Agora você deve poder pesquisar o recurso JNDI em qualquer lugar usando uma instância padrão do InitialContext :

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

Você também pode usar o JndiObjectFactoryBean do Spring para procurar o recurso:

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

Para aproveitar o contêiner DI, você também pode transformar o DataSource um 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(); } 

E agora você pode injetar o DataSource em qualquer bean Spring, como:

 @Autowired private DataSource jndiDataSource; 

Observe que muitos exemplos na Internet parecem desativar a pesquisa do recurso JNDI na boot:

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

Mas eu acho que é impotente, pois invoca logo após o afterPropertiesSet() que faz a pesquisa!