web-dev-qa-db-ja.com

埋め込みTomcatコンテナを使用してSpring BootでJNDIコンテキストを作成する方法

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

                    }
                });
            }
        }
    };
}

}

私はスプリングブートを使用しており、埋め込みの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でデータソースを確認できるアプリケーションに接続できます(カタリナ->リソース->コンテキスト-> "/"-> localhost-> javax.sql.DataSource-> jdbc/mydatasource)

また、JConsoleを介して表示されるMBeanもあります(Tomcat-> DataSource-> /-> localhost-> javax.sql.DataSource-> jdbc/mydatasource)

ただし、JNDIを介して実際にmydatasourceを探しているものを@ImportResourceで検索しても、見つかりません。

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

デフォルトでは、JNDIはNoInitialContextExceptionの原因となっている組み込みTomcatで無効になっています。有効にするには 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アプリのクラスローダーがスレッドコンテキストクラスローダーであるときにルックアップが実行されるようにする必要があります。これを実現するには、lookupOnStartupfalsejndiObjectFactoryBeanに設定する必要があります。 expectedTypejavax.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>

これにより、データソースのプロキシが作成され、実際のJNDIルックアップはアプリケーションコンテキストの起動時ではなく、最初の使用時に実行されます。

上記のアプローチは このSpring Bootサンプル に示されています。

54
Andy Wilkinson

結局のところ、wikisonaのおかげで答えが得られました。まず、Beans:

@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

12
nekperu15739

最近、Spring Bootに埋め込まれたTomcatでJNDIを使用する必要がありました。
実際の回答は、私のタスクを解決するための興味深いヒントを提供しますが、Spring Boot 2用に更新されていない可能性があるため、十分ではありませんでした。

Spring Boot 2.0.3.RELEASEでテストされた私の貢献を以下に示します。

実行時にクラスパスで利用可能なデータソースを指定

複数の選択肢があります:

  • dBCP 2データソースを使用する(時代遅れで効率の悪いDBCP 1を使用したくない)。
  • tomcat JDBCデータソースを使用します。
  • 他のデータソースを使用:たとえば、HikariCP。

それらのいずれも指定しない場合、デフォルト設定ではデータソースのインスタンス化は例外をスローします:

原因:javax.naming.NamingException:リソースファクトリインスタンスを作成できませんでした
 org.Apache.naming.factory.ResourceFactory.getDefaultFactory(ResourceFactory.Java:50)
 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)
 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)
 org.Apache.naming.NamingContext.lo okup(NamingContext.Java:173)
 at org.Apache.naming.SelectorContext.lookup(SelectorContext.Java:163)
 at javax.naming.InitialContext.lookup(InitialContext.Java:417) 
 org.springframework.jndi.JndiTemplate.lambda $ lookup $ 0(JndiTemplate.Java:156)
 at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.Java:91)
 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個の共通フレームが省略されました
原因: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省略されたフレーム
 
  • 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をカスタマイズする必要があります。
2つのこと:

  • デフォルトで無効になっている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()の直後に呼び出されるので、それは無力だと思います!

9
davidxxx

代わりに注意してください

public TomcatEmbeddedServletContainerFactory tomcatFactory()

次のメソッドシグネチャを使用する必要がありました

public EmbeddedServletContainerFactory embeddedServletContainerFactory() 
1
jerome

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からリソースをロードすることはできません。見つけようとします。

1
gotozero

データソースを@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;
0
Nick Grealy