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
デフォルトでは、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アプリのクラスローダーがスレッドコンテキストクラスローダーであるときにルックアップが実行されるようにする必要があります。これを実現するには、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>
これにより、データソースのプロキシが作成され、実際のJNDIルックアップはアプリケーションコンテキストの起動時ではなく、最初の使用時に実行されます。
上記のアプローチは このSpring Bootサンプル に示されています。
結局のところ、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
最近、Spring Bootに埋め込まれたTomcatでJNDIを使用する必要がありました。
実際の回答は、私のタスクを解決するための興味深いヒントを提供しますが、Spring Boot 2用に更新されていない可能性があるため、十分ではありませんでした。
Spring Boot 2.0.3.RELEASEでテストされた私の貢献を以下に示します。
実行時にクラスパスで利用可能なデータソースを指定
複数の選択肢があります:
それらのいずれも指定しない場合、デフォルト設定ではデータソースのインスタンス化は例外をスローします:
原因: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()
の直後に呼び出されるので、それは無力だと思います!
代わりに注意してください
public TomcatEmbeddedServletContainerFactory tomcatFactory()
次のメソッドシグネチャを使用する必要がありました
public EmbeddedServletContainerFactory embeddedServletContainerFactory()
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からリソースをロードすることはできません。見つけようとします。
データソースを@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;