こんにちは!今日はJNDIについて紹介します。それが何であるか、なぜ必要なのか、どのように機能するのか、どのように使用できるのかを見てみましょう。次に、Spring Boot 単体テストを作成し、その中でこの JNDI を操作します。
導入。ネーミングおよびディレクトリサービス
JNDI について説明する前に、ネーミング サービスとディレクトリ サービスとは何かを理解しましょう。このようなサービスの最もわかりやすい例は、PC、ラップトップ、またはスマートフォン上のファイル システムです。ファイル システムは (奇妙なことに) ファイルを管理します。このようなシステムのファイルはツリー構造にグループ化されます。各ファイルには一意の完全名が付いています (例: C:\windows\notepad.exe)。注: 完全なファイル名は、ルート ポイント (ドライブ C) からファイル自体 (notepad.exe) までのパスです。このようなチェーンの中間ノードはディレクトリ (Windows ディレクトリ) です。ディレクトリ内のファイルには属性があります。たとえば、「非表示」、「読み取り専用」などです。ファイル システムのような単純なものについて詳しく説明すると、ネーミング サービスやディレクトリ サービスの定義をより深く理解するのに役立ちます。したがって、名前とディレクトリ サービスは、多数の名前から多数のオブジェクトへのマッピングを管理するシステムです。私たちのファイル システムでは、オブジェクト (さまざまな形式のファイル自体) を隠すファイル名を操作します。ネーミングおよびディレクトリ サービスでは、名前付きオブジェクトがツリー構造に編成されます。そしてディレクトリオブジェクトには属性があります。名前およびディレクトリ サービスの別の例は、DNS (ドメイン ネーム システム) です。このシステムは、人間が読めるドメイン名 (https://javarush.com/ など) と機械が読める IP アドレス (18.196.51.113 など) の間のマッピングを管理します。DNS とファイル システム以外にも、次のようなサービスがたくさんあります。JNDI
JNDI (Java Naming and Directory Interface) は、ネーミング サービスとディレクトリ サービスにアクセスするための Java API です。JNDI は、Java プログラムがさまざまなネーミング サービスやディレクトリ サービスと対話するための統一メカニズムを提供する API です。内部では、JNDI と任意のサービスの間の統合は、サービス プロバイダー インターフェイス (SPI) を使用して実現されます。SPI を使用すると、さまざまなネーミング サービスやディレクトリ サービスを透過的に接続できるため、Java アプリケーションが JNDI API を使用して接続されたサービスにアクセスできるようになります。次の図は、JNDI アーキテクチャを示しています。JNDI。簡単な言葉で言うと意味
主な疑問は、なぜ JNDI が必要なのかということです。JNDI は、Java コードからのオブジェクトの「登録」から、このオブジェクトにバインドされたオブジェクトの名前によって Java オブジェクトを取得できるようにするために必要です。繰り返される言葉が多すぎて混乱しないように、上記のステートメントをテーゼに分割しましょう。- 最終的には Java オブジェクトを取得する必要があります。
- このオブジェクトをレジストリから取得します。
- このレジストリには多数のオブジェクトがあります。
- このレジストリ内の各オブジェクトには一意の名前が付いています。
- レジストリからオブジェクトを取得するには、リクエストで名前を渡す必要があります。あたかも「あなたがこれこれの名前で持っているものを私にください」と言っているかのようです。
- オブジェクトを名前でレジストリから読み取るだけでなく、オブジェクトを特定の名前でこのレジストリに保存することもできます (何らかの理由でオブジェクトはそこに保存されます)。
JNDI API
JNDI は Java SE プラットフォーム内で提供されます。JNDI を使用するには、JNDI クラスと、ネーミング サービスおよびディレクトリ サービスにアクセスするための 1 つ以上のサービス プロバイダをインポートする必要があります。JDK には、次のサービスのサービス プロバイダーが含まれています。- ライトウェイト ディレクトリ アクセス プロトコル (LDAP)。
- Common Object Request Broker Architecture (CORBA)。
- Common Object Services (COS) ネーム サービス。
- Java リモート メソッド呼び出し (RMI) レジストリ。
- ドメイン ネーム サービス (DNS)。
- javax.ネーミング;
- javax.naming.directory;
- javax.naming.ldap;
- javax.naming.event;
- javax.naming.spi.
インターフェース名
Name インターフェイスを使用すると、コンポーネント名と JNDI 命名構文を制御できます。JNDI では、すべての名前とディレクトリの操作はコンテキストに関連して実行されます。絶対的な根はありません。したがって、JNDI は、名前付けとディレクトリ操作の開始点を提供する InitialContext を定義します。初期コンテキストにアクセスすると、それを使用してオブジェクトや他のコンテキストを検索できます。Name objectName = new CompositeName("java:comp/env/jdbc");
上記のコードでは、オブジェクトが配置される名前を定義しました (オブジェクトは配置されていない可能性がありますが、それを期待しています)。最終的な目標は、このオブジェクトへの参照を取得し、プログラムで使用することです。したがって、名前はスラッシュで区切られた複数の部分 (またはトークン) で構成されます。このようなトークンはコンテキストと呼ばれます。一番最初のものは単なるコンテキストであり、後続のものはすべてサブコンテキスト (以下、サブコンテキストと呼びます) です。コンテキストは、ディレクトリやディレクトリ、または単なる通常のフォルダーに似ていると考えると理解しやすくなります。ルート コンテキストはルート フォルダーです。サブコンテキストはサブフォルダーです。次のコードを実行すると、指定された名前のすべてのコンポーネント (コンテキストとサブコンテキスト) を確認できます。
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
出力は次のようになります。
java:comp
env
jdbc
出力には、名前内のトークンがスラッシュで区切られていることが示されています (ただし、これについては前述しました)。各名前トークンには独自のインデックスがあります。トークンのインデックス付けは 0 から始まります。ルート コンテキストのインデックスは 0、次のコンテキストのインデックスは 1、次のコンテキストは 2 というようになります。インデックスによってサブコンテキスト名を取得できます。
System.out.println(objectName.get(1)); // -> env
追加のトークンを (インデックスの最後または特定の場所に) 追加することもできます。
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
メソッドの完全なリストは、公式ドキュメントに記載されています。
インターフェースコンテキスト
このインターフェイスには、コンテキストを初期化するための定数のセットと、コンテキストの作成と削除、オブジェクトの名前へのバインド、オブジェクトの検索と取得のためのメソッドのセットが含まれています。このインターフェイスを使用して実行される操作のいくつかを見てみましょう。最も一般的なアクションは、オブジェクトを名前で検索することです。これは次のメソッドを使用して行われます。Object lookup(String name)
Object lookup(Name name)
bind
。
void bind(Name name, Object obj)
void bind(String name, Object obj)
Object
名前からオブジェクトをバインドする、またはバインドを解除する逆の操作は、次のメソッドを使用して実行されますunbind
。
void unbind(Name name)
void unbind(String name)
初期コンテキスト
InitialContext
は、JNDI ツリーのルート要素を表し、 を実装するクラスですContext
。特定のノードに関連する JNDI ツリー内のオブジェクトを名前で検索する必要があります。ツリーのルート ノードは、そのようなノードとして機能しますInitialContext
。JNDI の一般的な使用例は次のとおりです。
- 得る
InitialContext
。 InitialContext
JNDI ツリーからオブジェクトを名前で取得するために使用します。
InitialContext
。それはすべて、Java プログラムが配置されている環境に依存します。たとえば、Java プログラムと JNDI ツリーが同じアプリケーション サーバー内で実行されている場合、以下をInitialContext
取得するのは非常に簡単です。
InitialContext context = new InitialContext();
そうでない場合は、コンテキストを取得することが少し難しくなります。場合によっては、コンテキストを初期化するために環境プロパティのリストを渡す必要があります。
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
Context ctx = new InitialContext(env);
上記の例は、コンテキストを初期化する可能な方法の 1 つを示しており、他のセマンティックな負荷はかかりません。コードを詳しく調べる必要はありません。
SpringBoot 単体テスト内での JNDI の使用例
上で、JNDI がネーミング サービスおよびディレクトリ サービスと対話するには、SPI (サービス プロバイダ インターフェイス) が必要であり、これを利用して Java とネーミング サービスの間の統合が実行されると述べました。標準の JDK にはいくつかの異なる SPI (上にリストしました) が付属していますが、それぞれの SPI はデモンストレーションの目的にはあまり関係ありません。コンテナ内で JNDI および Java アプリケーションを起動するのは、いくぶん興味深いものです。ただし、この記事の著者は怠け者なので、JNDI がどのように機能するかを示すために、最も抵抗の少ない方法を選択しました。それは、SpringBoot アプリケーション単体テスト内で JNDI を実行し、Spring Framework からの小さなハックを使用して JNDI コンテキストにアクセスするというものです。そこで、私たちの計画は次のとおりです。- 空の Spring Boot プロジェクトを作成してみましょう。
- このプロジェクト内に単体テストを作成しましょう。
- テスト内では、JNDI の操作をデモンストレーションします。
- コンテキストへのアクセスを取得します。
- JNDI で何らかの名前でオブジェクトをバインド (バインド) します。
- オブジェクトを名前で取得します (ルックアップ)。
- オブジェクトが null でないことを確認してみましょう。
- JDBC API。
- H2 Dデータベース。
SimpleNamingContextBuilder
。このクラスは、単体テストまたはスタンドアロン アプリケーション内で JNDI を簡単に起動できるように設計されています。コンテキストを取得するコードを書いてみましょう。
final SimpleNamingContextBuilder simpleNamingContextBuilder
= new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();
final InitialContext context = new InitialContext();
コードの最初の 2 行を使用すると、後で JNDI コンテキストを簡単に初期化できます。これらがないと、InitialContext
インスタンスの作成時に例外がスローされます: javax.naming.NoInitialContextException
。免責事項。このクラスはSimpleNamingContextBuilder
非推奨クラスです。この例は、JNDI を使用する方法を示すことを目的としています。これらは、単体テスト内で JNDI を使用するためのベスト プラクティスではありません。これは、コンテキストを構築し、JNDI からのオブジェクトのバインドと取得を実証するための要点であると言えます。コンテキストを受け取ると、そこからオブジェクトを抽出したり、コンテキスト内のオブジェクトを検索したりできます。JNDI にはまだオブジェクトがないため、そこに何かを置くのは論理的です。例えば、DriverManagerDataSource
:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
この行では、クラス オブジェクトをDriverManagerDataSource
name にバインドしていますjava:comp/env/jdbc/datasource
。次に、コンテキストから名前を指定してオブジェクトを取得できます。コンテキスト内には他にオブジェクトがないため、今配置したオブジェクトを取得する以外に選択肢はありません =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
ここで、DataSource に接続があることを確認してみましょう (connection、connection、または connection は、データベースと連携するように設計された Java クラスです)。
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
すべてが正しく行われた場合、出力は次のようになります。
conn1: url=jdbc:h2:mem:mydb user=
コードの一部の行では例外がスローされる可能性があることに注意してください。次の行がスローされますjavax.naming.NamingException
。
simpleNamingContextBuilder.activate()
new InitialContext()
context.bind(...)
context.lookup(...)
DataSource
それをスローできますjava.sql.SQLException
。この点に関しては、ブロック内でコードを実行するtry-catch
か、テストユニットのシグネチャで例外をスローできることを示す必要があります。テスト クラスの完全なコードは次のとおりです。
@SpringBootTest
class JndiExampleApplicationTests {
@Test
void contextLoads() {
try {
final SimpleNamingContextBuilder simpleNamingContextBuilder
= new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();
final InitialContext context = new InitialContext();
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
} catch (SQLException | NamingException e) {
e.printStackTrace();
}
}
}
テストを実行すると、次のログが表示されます。
o.s.m.jndi.SimpleNamingContextBuilder : Activating simple JNDI environment
o.s.mock.jndi.SimpleNamingContext : Static JNDI binding: [java:comp/env/jdbc/datasource] = [org.springframework.jdbc.datasource.DriverManagerDataSource@4925f4f5]
conn1: url=jdbc:h2:mem:mydb user=
GO TO FULL VERSION