如何使用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
默认情况下,在导致NoInitialContextException
embedded式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;