如何使用embedded式Tomcat容器在Spring Boot中创buildJNDI上下文

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

}

我正在使用spring引导,并尝试使用embedded式tomcat启动,为我的数据源创build一个JNDI上下文:

  <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> 

如果我删除@ImportResource,我的应用程序启动就好了。 我可以连接到tomcat实例。 我可以检查所有的执行器端点。 使用JConsole,我可以连接到应用程序,我可以在MBean中看到我的数据源(Catalina – > Resource – > Context – >“/” – > localhost – > javax.sql.DataSource – > jdbc / mydatasource)

我还通过JConsole在这里(Tomcat – > DataSource – > / – > localhost – > javax.sql.DataSource – > jdbc / mydatasource)显示了MBean。

但是,当我@ImportResource实际上通过JNDI查找mydatasource时,它没有find它。

 <bean id="myDS" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/mydatasource"/> </bean> 

我导入的XML文件的相关部分

上面configuration的ContextResource与在应用程序部署到tomcat容器时部署的context.xml中使用的参数完全相同。 我导入的bean和我的应用程序在部署到tomcat容器时正常工作。

所以现在看来​​我有一个背景,但是看起来命名是不正确的。 我已经尝试了资源名称的各种组合,但似乎无法在此上下文中生成“comp”边界。

 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 

默认情况下,在导致NoInitialContextExceptionembedded式Tomcat中禁用JNDI。 您需要调用Tomcat.enableNaming()来启用它。 最简单的方法是使用TomcatEmbeddedServletContainer子类:

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

如果采用这种方法,则还可以通过在TomcatEmbeddedServletContainerFactory子类中重写postProcessContext方法来在JNDI中注册DataSource

context.getNamingResources().addResource将资源添加到java:comp/env上下文,以便资源的名称应该是jdbc/mydatasource而不是java:comp/env/mydatasource

Tomcat使用线程上下文类加载器来确定应该对哪个JNDI上下文进行查找。 您将资源绑定到Web应用程序的JNDI上下文中,因此您需要确保在Web应用程序的类加载器是线程上下文类加载器时执行查找。 您应该能够通过在jndiObjectFactoryBean设置为false来实现此jndiObjectFactoryBean 。 您还需要将expectedType设置为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> 

这将为DataSource创build一个代理,实际的JNDI查询是在第一次使用时执行的,而不是在应用程序上下文启动期间执行的。

上面介绍的方法在这个Spring Boot示例中进行了说明 。

毕竟我得到了答案感谢wikisona,首先豆类:

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

完整的代码在这里: https : //github.com/wilkinsona/spring-boot-sample-tomcat-jndi

请注意,而不是

 public TomcatEmbeddedServletContainerFactory tomcatFactory() 

我不得不使用下面的方法签名

 public EmbeddedServletContainerFactory embeddedServletContainerFactory() 

你有没有试过@Lazy加载数据源? 因为你在Spring上下文中初始化你的embedded式Tomcat容器,所以你必须延迟你的DataSource的初始化(直到JNDIvariables被设置)。

NB我还没有机会testing这个代码呢!

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

您可能还需要在使用DataSource的任何地方添加@Lazy注释。 例如

 @Lazy @Autowired private DataSource dataSource; 
Interesting Posts