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 boot并尝试使用嵌入式tomcat启动,为我的数据源创建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,我可以连接到我可以在MBeans中看到我的数据源的应用程序(Catalina - &gt;资源 - &gt;上下文 - &gt;&#34; /&#34; - &gt; localhost - &gt; javax.sql。 DataSource - &gt; jdbc / mydatasource)
我也通过JConsole在这里显示MBean(Tomcat - &gt; DataSource - &gt; / - &gt; localhost - &gt; javax.sql.DataSource - &gt; jdbc / mydatasource)
然而,当我@ImportResource通过JNDI实际寻找mydatasource时,它找不到它。
<bean id="myDS" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/mydatasource"/>
</bean>
导入的xml文件的相关部分
我在上面配置的ContextResource与我在context.xml中使用的参数完全相同,当将应用程序部署到tomcat容器时,该参数将被部署。部署到tomcat容器时,我的导入bean和我的应用程序正常工作。
所以看来我现在有了一个上下文,但它看起来并不合适。我尝试过各种资源名称组合,但似乎无法生成&#34; comp&#34;在这种情况下受到约束。
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
答案 0 :(得分:54)
默认情况下,嵌入式Tomcat中禁用JNDI,导致NoInitialContextException
。您需要致电Tomcat.enableNaming()
启用它。最简单的方法是使用TomcatEmbeddedServletContainer
子类:
@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
@Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}
如果采用这种方法,您还可以通过覆盖DataSource
子类中的postProcessContext
方法在JNDI中注册TomcatEmbeddedServletContainerFactory
。
context.getNamingResources().addResource
将资源添加到java:comp/env
上下文中,因此资源的名称应为jdbc/mydatasource
而不是java:comp/env/mydatasource
。
Tomcat使用线程上下文类加载器来确定应该对哪个JNDI上下文执行查找。您将资源绑定到Web应用程序的JNDI上下文中,因此您需要确保在Web应用程序的类加载器是线程上下文类加载器时执行查找。您应该可以通过在lookupOnStartup
上将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创建一个代理,在第一次使用时执行实际的JNDI查找,而不是在应用程序上下文启动期间执行。
上述方法在this Spring Boot sample中说明。
答案 1 :(得分:11)
毕竟我得到了答案,感谢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
答案 2 :(得分:9)
最近,我要求在Spring Boot中将JNDI与嵌入式Tomcat一起使用。
实际答案给出了一些有趣的提示来解决我的任务,但这还不够,因为可能没有针对Spring Boot 2更新。
这是我用Spring Boot 2.0.3.RELEASE测试的贡献。
在运行时指定类路径中可用的数据源
您有多种选择:
如果您不指定任何一个,则使用默认配置,数据源的实例化将引发异常:
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
要使用Apache JDBC数据源,无需添加任何依赖关系,但必须将默认工厂类更改为org.apache.tomcat.jdbc.pool.DataSourceFactory
。
您可以在资源声明中做到这一点:
resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
我将在下面解释在何处添加此行。
要使用DBCP 2数据源,必须具有依赖性:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.5.4</version>
</dependency>
当然,请根据您的Spring Boot Tomcat嵌入式版本调整构件版本。
要使用HikariCP,请添加所需的依赖项(如果您的配置中不存在)(例如,如果您依赖Spring Boot的持久性启动器),例如:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.1.0</version>
</dependency>
并在资源声明中指定随附的工厂:
resource.setProperty("factory", "com.zaxxer.hikari.HikariDataSource");
数据源配置/声明
您必须自定义创建TomcatServletWebServerFactory
实例的bean。
有两件事要做:
启用默认情况下禁用的JNDI命名
在服务器上下文中创建并添加JNDI资源
例如对于PostgreSQL和DBCP 2数据源,请执行以下操作:
@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);
}
};
}
这是Tomcat JDBC和HikariCP数据源的变体。
在postProcessContext()
中,按照前面针对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);
}
};
对于HikariCP:
@Override
protected void postProcessContext(Context context) {
ContextResource resource = new ContextResource();
//...
resource.setProperty("factory", "com.zaxxer.hikari.HikariDataSource");
//...
context.getNamingResources()
.addResource(resource);
}
};
使用/注入数据源
您现在应该可以使用标准的InitialContext
实例在任何地方查找JNDI资源:
InitialContext initialContext = new InitialContext();
DataSource datasource = (DataSource) initialContext.lookup("java:comp/env/jdbc/myJndiResource");
您还可以使用Spring的JndiObjectFactoryBean
来查找资源:
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:comp/env/jdbc/myJndiResource");
bean.afterPropertiesSet();
DataSource object = (DataSource) bean.getObject();
要利用DI容器,您还可以使DataSource
成为Spring bean:
@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();
}
因此您现在可以将DataSource注入任何Spring Bean中,例如:
@Autowired
private DataSource jndiDataSource;
请注意,互联网上的许多示例似乎在启动时禁用了JNDI资源的查找:
bean.setJndiName("java:comp/env/jdbc/myJndiResource");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
但是我认为它是无助的,因为它只是在afterPropertiesSet()
之后进行查找而调用!
答案 3 :(得分:1)
请注意,而不是
public TomcatEmbeddedServletContainerFactory tomcatFactory()
我必须使用以下方法签名
public EmbeddedServletContainerFactory embeddedServletContainerFactory()
答案 4 :(得分:1)
在Spring Boot 2.1中,我找到了另一个解决方案。 扩展标准工厂类方法getTomcatWebServer。然后从任何地方将其作为bean返回。
public class CustomTomcatServletWebServerFactory extends TomcatServletWebServerFactory {
@Override
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
System.setProperty("catalina.useNaming", "true");
tomcat.enableNaming();
return new TomcatWebServer(tomcat, getPort() >= 0);
}
}
@Component
public class TomcatConfiguration {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new CustomTomcatServletWebServerFactory();
return factory;
}
虽然无法从context.xml加载资源。会尝试找出答案。
答案 5 :(得分:0)
您是否尝试@Lazy
加载数据源?因为您在Spring上下文中初始化嵌入式Tomcat容器,所以必须延迟DataSource
的初始化(直到设置JNDI变量)。
N.B。我还没有机会测试这段代码!
@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
注释。 e.g。
@Lazy
@Autowired
private DataSource dataSource;