如何使用嵌入式 Tomcat 容器在 Spring Boot 中创建 JNDI 上下文 [英] How to create JNDI context in Spring Boot with Embedded Tomcat Container

查看:37
本文介绍了如何使用嵌入式 Tomcat 容器在 Spring Boot 中创建 JNDI 上下文的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

import org.apache.catalina.Context;导入 org.apache.catalina.deploy.ContextResource;导入 org.apache.catalina.startup.Tomcat;导入 org.springframework.boot.autoconfigure.EnableAutoConfiguration;导入 org.springframework.boot.builder.SpringApplicationBuilder;导入 org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;导入 org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;导入 org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;导入 org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer;导入 org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;导入 org.springframework.context.annotation.Bean;导入 org.springframework.context.annotation.ComponentScan;导入 org.springframework.context.annotation.Configuration;导入 org.springframework.context.annotation.ImportResource;@配置@EnableAutoConfiguration@ComponentScan@ImportResource("classpath:applicationContext.xml")公共类应用{public static void main(String[] args) 抛出异常 {新的 SpringApplicationBuilder().showBanner(假).sources(Application.class).run(args);}@豆角,扁豆公共 TomcatEmbeddedServletContainerFactory tomcatFactory() {返回新的 TomcatEmbeddedServletContainerFactory() {@覆盖受保护的 TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {tomcat.enableNaming();返回 super.getTomcatEmbeddedServletContainer(tomcat);}};}@豆角,扁豆公共 EmbeddedServletContainerCustomizer EmbeddedServletContainerCustomizer() {返回新的 EmbeddedServletContainerCustomizer() {@覆盖公共无效定制(ConfigurableEmbeddedServletContainer容器){如果(TomcatEmbeddedServletContainerFactory的容器实例){TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = (TomcatEmbeddedServletContainerFactory) 容器;tomcatEmbeddedServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer() {@覆盖公共无效定制(上下文上下文){ContextResource mydatasource = new ContextResource();mydatasource.setName("jdbc/mydatasource");mydatasource.setAuth("容器");mydatasource.setType("javax.sql.DataSource");mydatasource.setScope("可共享");mydatasource.setProperty("driverClassName", "oracle.jdbc.driver.OracleDriver");mydatasource.setProperty("url", "jdbc:oracle:thin:@mydomain.com:1522:myid");mydatasource.setProperty("用户名", "我的用户名");mydatasource.setProperty("password", "mypassword");context.getNamingResources().addResource(mydatasource);}});}}};}

}

我正在使用 spring boot 并尝试使用为我的数据源创建 JNDI 上下文的嵌入式 tomcat 启动:

 <groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId><version>1.1.4.RELEASE</version></依赖><依赖><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><version>1.1.4.RELEASE</version></依赖><依赖><groupId>org.springframework.data</groupId><artifactId>spring-data-oracle</artifactId><version>1.0.0.RELEASE</version></依赖>

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

我也通过 JConsole 在这里显示了 MBeans (Tomcat -> DataSource ->/-> localhost -> javax.sql.DataSource -> jdbc/mydatasource)

但是,当我 @ImportResource 实际通过 JNDI 寻找 mydatasource 时,它​​没有找到它.

<property name="jndiName" value="java:comp/env/jdbc/mydatasource"/></bean>

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

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

所以看起来我现在有一个上下文,但命名似乎不正确.我尝试了资源名称的各种组合,但似乎无法在此上下文中生成comp"绑定.

Caused by: javax.naming.NameNotFoundException: Name [comp/env/jdbc/mydatasource] 在此上下文中未绑定.无法找到 [comp].在 org.apache.naming.NamingContext.lookup(NamingContext.java:819)在 org.apache.naming.NamingContext.lookup(NamingContext.java:167)在 org.apache.naming.SelectorContext.lookup(SelectorContext.java:156)在 javax.naming.InitialContext.lookup(InitialContext.java:392)在 org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.java:155)在 org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:87)在 org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:152)在 org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179)在 org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:95)在 org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:106)在 org.springframework.jndi.JndiObjectFactoryBean.lookupWithFallback(JndiObjectFactoryBean.java:231)在 org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:217)在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612)在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549)... 30 多个

解决方案

默认情况下,JNDI 在嵌入式 Tomcat 中是禁用的,这会导致 NoInitialContextException.你需要调用Tomcat.enableNaming() 启用它.最简单的方法是使用 TomcatEmbeddedServletContainer 子类:

@Bean公共 TomcatEmbeddedServletContainerFactory tomcatFactory() {返回新的 TomcatEmbeddedServletContainerFactory() {@覆盖受保护的 TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {tomcat.enableNaming();返回 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 上将 lookupOnStartup 设置为 false 来实现这一点.您还需要将 expectedType 设置为 javax.sql.DataSource:

<property name="jndiName" value="java:comp/env/jdbc/mydatasource"/><property name="expectedType" value="javax.sql.DataSource"/><property name="lookupOnStartup" value="false"/></bean>

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

上述方法在这个 Spring Boot 示例中进行了说明.

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

                    }
                });
            }
        }
    };
}

}

I'm using spring boot and trying to startup with an embedded tomcat that creates a JNDI context for my datasources:

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

If I remove the @ImportResource my application starts up just fine. I can connect to the tomcat instance. I can check all of my actuator endpoints. Using JConsole, I can connect to the application I can see my datasource in the MBeans (Catalina -> Resource -> Context -> "/" -> localhost -> javax.sql.DataSource -> jdbc/mydatasource)

I also have MBeans showing up, via JConsole, here (Tomcat -> DataSource -> / -> localhost -> javax.sql.DataSource -> jdbc/mydatasource)

However, when I @ImportResource what is actually looking for mydatasource via JNDI, it's not finding it.

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

The relevant part of my imported xml file

The ContextResource that I'm configuring above is with the exact same parameters that I was using in the context.xml that is getting deployed when the application is deployed to a tomcat container. My imported beans and my application are working properly when deployed to a tomcat container.

So it appears that I have a context now, but it doesn't appear that the naming is right. I've tried to various combinations of the resource name, but can't seem to generate a "comp" bound in this context.

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

解决方案

By default, JNDI is disabled in embedded Tomcat which is causing the NoInitialContextException. You need to call Tomcat.enableNaming() to enable it. The easiest way to do that is with a TomcatEmbeddedServletContainer subclass:

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(tomcat);
        }
    };
}

If you take this approach, you can also register the DataSource in JNDI by overriding the postProcessContext method in your TomcatEmbeddedServletContainerFactory subclass.

context.getNamingResources().addResource adds the resource to the java:comp/env context so the resource's name should be jdbc/mydatasource not java:comp/env/mydatasource.

Tomcat uses the thread context class loader to determine which JNDI context a lookup should be performed against. You're binding the resource into the web app's JNDI context so you need to ensure that the lookup is performed when the web app's class loader is the thread context class loader. You should be able to achieve this by setting lookupOnStartup to false on the jndiObjectFactoryBean. You'll also need to set expectedType to 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>

This will create a proxy for the DataSource with the actual JNDI lookup being performed on first use rather than during application context startup.

The approach described above is illustrated in this Spring Boot sample.

这篇关于如何使用嵌入式 Tomcat 容器在 Spring Boot 中创建 JNDI 上下文的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆