Spring Boot 如何使用嵌入式Tomcat容器在Sping Boot 中创建JNDI上下文

mf98qq94  于 2023-06-05  发布在  Spring
关注(0)|答案(7)|浏览(362)
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,我可以连接到应用程序,我可以在MBean中看到我的数据源( Catalina -> Resource -> Context ->“/”-> localhost -> javax.sql.DataSource -> jdbc/mydatasource)
我还通过JConsole显示了MBean(Tomcat -> DataSource -> / -> localhost -> javax.sql.DataSource -> 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的参数与我在将应用程序部署到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
du7egjpx

du7egjpx1#

默认情况下,JNDI在嵌入式Tomcat中是禁用的,这会导致NoInitialContextException。您需要调用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上将lookupOnStartup设置为false来实现这一点。您还需要将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中说明。

ntjbwcob

ntjbwcob2#

我最近需要在Sping Boot 中使用带有嵌入式Tomcat的JNDI。
实际的答案给予了一些有趣的提示来解决我的任务,但这还不够,因为可能没有为Sping Boot 2更新。
这是我在Sping Boot 2.0.3.RELEASE上测试的贡献。

指定运行时类路径中可用的数据源

您有多种选择:

  • 使用DBCP 2数据源(您不希望使用过时且效率较低的DBCP 1)。
  • 使用Tomcat JDBC数据源。
  • 使用任何其他数据源:例如HikariCP。

如果你没有指定它们中的任何一个,使用默认配置,数据源的示例化将抛出一个异常:

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>
当然,根据您的Sping Boot Tomcat嵌入式版本调整工件版本。

  • 若要使用HikariCP,请添加所需的依赖项,如果您的配置中尚未存在(如果您依赖Sping Boot 的持久化启动器,则可能会这样),例如:

<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.1.0</version> </dependency>
并指定在资源声明中附带的工厂:

resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory");

数据源配置/声明

您必须自定义创建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();
}

所以你现在可以在任何Spring bean中注入DataSource,比如:

@Autowired
private DataSource jndiDataSource;

请注意,互联网上的许多示例似乎都禁用了在启动时查找JNDI资源:

bean.setJndiName("java:comp/env/jdbc/myJndiResource");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();

但我认为这是无助的,因为它调用后,afterPropertiesSet(),做查找!

yzckvree

yzckvree3#

毕竟我得到了答案感谢维基索纳,首先是豆子:

@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

oknwwptz

oknwwptz4#

在SpringBoot 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加载资源不起作用。我会尽力查出来的。

hmtdttj4

hmtdttj45#

请注意,而不是

public TomcatEmbeddedServletContainerFactory tomcatFactory()

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

public EmbeddedServletContainerFactory embeddedServletContainerFactory()
nvbavucw

nvbavucw6#

你试过@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注解。例如

@Lazy
@Autowired
private DataSource dataSource;
mftmpeh8

mftmpeh87#

在Sping Boot v3中,以前的解决方案似乎不再可行。
在Spring文档中,我采用了这种方法:
1.创建WebServerFactoryCustomizer并创建您的jndi资源
1.添加生命周期侦听器以启用命名
1.添加tomcatjdbc依赖项
定制者:

@Component
public class MyDatasourceJndiCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

  @Value("${mydatasource.jndi.name}")
  private String jndiName;

  @Value("${mydatasource.jndi.driver-class-name}")
  private String driverClassName;

  @Value("${mydatasource.jndi.url}")
  private String url;

  @Value("${mydatasource.username}")
  private String username;

  @Value("${jndi.password}")
  private String password;

  @Override
  public void customize(TomcatServletWebServerFactory server) {
    server.addContextCustomizers(new TomcatContextCustomizer() {
      @Override
      public void customize(Context context) {
        ContextResource resource = new ContextResource();
        resource.setName(jndiName);
        resource.setType(DataSource.class.getName());
        resource.setProperty("driverClassName", driverClassName);
        resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
        resource.setProperty("url", url);
        resource.setProperty("username", username);
        resource.setProperty("password", password);
        context.getNamingResources()
          .addResource(resource);
      }

    });

    enableNaming(server);
  }

  private static void enableNaming(TomcatServletWebServerFactory server) {
    server.addContextLifecycleListeners(new NamingContextListener());
    
    // The following code is copied from Tomcat 
    System.setProperty("catalina.useNaming", "true");
    String value = "org.apache.naming";
    String oldValue = System.getProperty("java.naming.factory.url.pkgs");
    if (oldValue != null) {
      if (oldValue.contains(value)) {
        value = oldValue;
      } else {
        value = value + ":" + oldValue;
      }
    }

    System.setProperty("java.naming.factory.url.pkgs", value);
    value = System.getProperty("java.naming.factory.initial");
    if (value == null) {
      System.setProperty("java.naming.factory.initial", "org.apache.naming.java.javaURLContextFactory");
    }
  }

}

依赖性:

<dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-jdbc</artifactId>
      <version>10.1.9</version>
    </dependency>

相关问题