【问题标题】:Howto use JNDI database connection with Spring Boot and Spring Data using embedded Tomcat?如何使用嵌入式 Tomcat 将 JNDI 数据库连接与 Spring Boot 和 Spring Data 结合使用?
【发布时间】:2015-03-05 12:49:25
【问题描述】:

当我尝试使用嵌入式 Tomcat 服务器将 JNDI 数据源与 Spring Boot 和 Spring Data JPA 一起使用时,在使用 SpringApplication.run 运行应用程序时收到以下错误消息:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Instantiation of bean failed; 
nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration.entityManagerFactory(org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilder)] threw exception;
nested exception is org.springframework.jndi.JndiLookupFailureException: JndiObjectTargetSource failed to obtain new target object; 
nested exception is javax.naming.NameNotFoundException: Name [comp/env/jdbc/myDataSource] is not bound in this Context. Unable to find [comp].

我使用How to create JNDI context in Spring Boot with Embedded Tomcat Container的解决方案中描述的配置

唯一的区别是对 org.springframework.boot:spring-boot-starter-data-jpa 的附加 Maven 依赖

这是一个示例项目:https://github.com/derkoe/spring-boot-sample-tomcat-jndi(这是解决方案中示例的修改版本)。只需检查、构建并运行 SampleTomcatJndiApplication。

看起来用于查找数据库连接的 JNDI 上下文还不是来自 webapp 的上下文。这似乎是 Spring 上下文和 Tomcat 服务器初始化时的排序问题。

有什么办法解决这个问题吗?

【问题讨论】:

    标签: spring-boot embedded-tomcat-8


    【解决方案1】:

    Tomcat 使用线程的上下文类加载器来确定要执行查找的 JNDI 上下文。如果线程上下文类加载器不是 Web 应用类加载器,则 JNDI 上下文为空,因此查找失败。

    问题是在启动期间执行的DataSource 的 JNDI 查找是在主线程上执行的,并且主线程的 TCCL 不是 Tomcat 的 Web 应用程序类加载器。您可以通过更新TomcatEmbeddedServletContainerFactory bean 来设置线程上下文类加载器来解决此问题。我还没有说服自己这不是一个可怕的 hack,但它确实有效……

    这是更新的 bean:

    @Bean
    public TomcatEmbeddedServletContainerFactory tomcatFactory() {
        return new TomcatEmbeddedServletContainerFactory() {
    
            @Override
            protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                    Tomcat tomcat) {
                tomcat.enableNaming();
                TomcatEmbeddedServletContainer container = 
                        super.getTomcatEmbeddedServletContainer(tomcat);
                for (Container child: container.getTomcat().getHost().findChildren()) {
                    if (child instanceof Context) {
                        ClassLoader contextClassLoader = 
                                ((Context)child).getLoader().getClassLoader();
                        Thread.currentThread().setContextClassLoader(contextClassLoader);
                        break;
                    }
                }
                return container;
            }
    
            @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);
            }
        };
    }
    

    getEmbeddedServletContainer 提取上下文的类加载器并将其设置为当前线程的上下文类加载器。这发生在 调用 super 方法之后。这种排序很重要,因为对 super 方法的调用会创建并启动容器,并且作为该创建的一部分,会创建上下文的类加载器。

    【讨论】:

    • 这绝对是一个 hack - 但它有效! Spring Boot 团队有什么更好的主意吗?
    • 还有一个“更简单”的技巧:您可以从容器中获取上下文:container.getTomcat().getHost().findChild(""); 无需存储在实例变量中。
    • 我是 Boot 团队的一员
    • 我已经打开an issue 讨论我们是否应该为您设置 TCCL。
    • @dustin.schultz 这是在哪里合并的?谢谢!
    【解决方案2】:

    根据@Andy Wilkinson 的出色回答,我更新了 SpringBoot 2.5.3 的解决方案:

       public TomcatServletWebServerFactory tomcatFactory() {
            return new TomcatServletWebServerFactory() {
                @Override
                protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
                    tomcat.enableNaming();
                    TomcatWebServer tomcatWebServer = super.getTomcatWebServer(tomcat);
                    Container context = tomcatWebServer.getTomcat().getHost().findChild(getContextPath());
                    ClassUtils.overrideThreadContextClassLoader(((Context) context).getLoader().getClassLoader());
                    return tomcatWebServer;
                }
    
                @Override
                protected void postProcessContext(Context context) {
                     ...
                }
       }
    

    【讨论】:

      猜你喜欢
      • 2015-03-23
      • 2019-12-17
      • 2015-10-07
      • 1970-01-01
      • 2016-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-01
      相关资源
      最近更新 更多