JavaRush /Java Blog /Random-JA /Java での JNDI の使用
Анзор Кармов
レベル 31
Санкт-Петербург

Java での JNDI の使用

Random-JA グループに公開済み
こんにちは!今日はJNDIについて紹介します。それが何であるか、なぜ必要なのか、どのように機能するのか、どのように使用できるのかを見てみましょう。次に、Spring Boot 単体テストを作成し、その中でこの JNDI を操作します。 Java での JNDI の使用 - 1

導入。ネーミングおよびディレクトリサービス

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 アーキテクチャを示しています。 Java での JNDI の使用 - 2

出典: Oracle Java チュートリアル

JNDI。簡単な言葉で言うと意味

主な疑問は、なぜ JNDI が必要なのかということです。JNDI は、Java コードからのオブジェクトの「登録」から、このオブジェクトにバインドされたオブジェクトの名前によって Java オブジェクトを取得できるようにするために必要です。繰り返される言葉が多すぎて混乱しないように、上記のステートメントをテーゼに分割しましょう。
  1. 最終的には Java オブジェクトを取得する必要があります。
  2. このオブジェクトをレジストリから取得します。
  3. このレジストリには多数のオブジェクトがあります。
  4. このレジストリ内の各オブジェクトには一意の名前が付いています。
  5. レジストリからオブジェクトを取得するには、リクエストで名前を渡す必要があります。あたかも「あなたがこれこれの名前で持っているものを私にください」と言っているかのようです。
  6. オブジェクトを名前でレジストリから読み取るだけでなく、オブジェクトを特定の名前でこのレジストリに保存することもできます (何らかの理由でオブジェクトはそこに保存されます)。
したがって、ある種のレジストリ、オブジェクト ストレージ、または JNDI ツリーがあります。次に、例を使用して、JNDI の意味を理解してみましょう。ほとんどの場合、JNDI はエンタープライズ開発で使用されることに注意してください。そして、そのようなアプリケーションは何らかのアプリケーションサーバー内で動作します。このサーバーは、Java EE アプリケーション サーバー、Tomcat などのサーブレット コンテナ、またはその他のコンテナである可能性があります。オブジェクト レジストリ自体、つまり JNDI ツリーは通常、このアプリケーション サーバー内にあります。後者は必ずしも必要というわけではありません (そのようなツリーをローカルに置くことができます) が、最も一般的です。JNDI ツリーは、オブジェクトを名前とともに「レジストリに保存」する特別な担当者 (システム管理者または DevOps スペシャリスト) によって管理できます。アプリケーションと JNDI ツリーが同じコンテナ内に同じ場所に配置されている場合、そのようなレジストリに格納されている Java オブジェクトに簡単にアクセスできます。さらに、レジストリとアプリケーションは、異なるコンテナーや異なる物理マシンに配置することもできます。この場合でも、JNDI を使用すると Java オブジェクトにリモートからアクセスできます。典型的なケースです。Java EE サーバー管理者は、データベースへの接続に必要な情報を格納するオブジェクトをレジストリに配置します。したがって、データベースを操作するには、JNDI ツリーから目的のオブジェクトをリクエストして、それを操作するだけです。とても快適です。エンタープライズ開発にはさまざまな環境があるという事実からも利便性が高まります。運用サーバーとテスト サーバーがあります (テスト サーバーが複数あることもよくあります)。次に、JNDI 内の各サーバー上のデータベースに接続するためのオブジェクトを配置し、アプリケーション内でこのオブジェクトを使用することで、アプリケーションをあるサーバー (テスト、リリース) から別のサーバーにデプロイするときに何も変更する必要がなくなります。どこからでもデータベースにアクセスできるようになります。もちろん、この例は多少簡略化されていますが、JNDI が必要な理由をよりよく理解するのに役立つことを願っています。次に、いくつかの攻撃的な要素を交えながら、Java の JNDI について詳しく見ていきます。

JNDI API

JNDI は Java SE プラットフォーム内で提供されます。JNDI を使用するには、JNDI クラスと、ネーミング サービスおよびディレクトリ サービスにアクセスするための 1 つ以上のサービス プロバイダをインポートする必要があります。JDK には、次のサービスのサービス プロバイダーが含まれています。
  • ライトウェイト ディレクトリ アクセス プロトコル (LDAP)。
  • Common Object Request Broker Architecture (CORBA)。
  • Common Object Services (COS) ネーム サービス。
  • Java リモート メソッド呼び出し (RMI) レジストリ。
  • ドメイン ネーム サービス (DNS)。
JNDI API コードはいくつかのパッケージに分割されています。
  • javax.ネーミング;
  • javax.naming.directory;
  • javax.naming.ldap;
  • javax.naming.event;
  • javax.naming.spi.
JNDI の紹介は、主要な JNDI 機能を含む 2 つのインターフェイス (名前とコンテキスト) から始めます。

インターフェース名

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)
どちらのメソッドも名前 name をオブジェクトにバインドします。Object 名前からオブジェクトをバインドする、またはバインドを解除する逆の操作は、次のメソッドを使用して実行されますunbind
  • void unbind(Name name)
  • void unbind(String name)
メソッドの完全なリストは、公式ドキュメント Web サイトで入手できます。

初期コンテキスト

InitialContextは、JNDI ツリーのルート要素を表し、 を実装するクラスですContext。特定のノードに関連する JNDI ツリー内のオブジェクトを名前で検索する必要があります。ツリーのルート ノードは、そのようなノードとして機能しますInitialContext。JNDI の一般的な使用例は次のとおりです。
  • 得るInitialContext
  • InitialContextJNDI ツリーからオブジェクトを名前で取得するために使用します。
入手するにはいくつかの方法があります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 でないことを確認してみましょう。
順番に始めましょう。 ファイル -> 新規 -> プロジェクト... Java での JNDI の使用 - 3次に、Spring Initializr項目を選択します。 Java での JNDI の使用 - 4プロジェクトに関するメタデータを入力します。 Java での JNDI の使用 - 5次に、必要な Spring Framework コンポーネントを選択します。いくつかの DataSource オブジェクトをバインドするので、データベースと連携するコンポーネントが必要です。
  • JDBC API。
  • H2 Dデータベース。
Java での JNDI の使用 - 6ファイル システム内の場所を決定しましょう。 Java での JNDI の使用 - 7これでプロジェクトが作成されます。実際、1 つの単体テストが自動的に生成されました。これをデモンストレーションの目的で使用します。以下は、プロジェクトの構造と必要なテストです。 Java での JNDI の使用 - 8contextLoads テスト内でコードを書き始めましょう。上で説明した Spring の小さなハックは、 クラス です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"));
この行では、クラス オブジェクトをDriverManagerDataSourcename にバインドしています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=

結論

今日はJNDIについて調べました。ネーミング サービスとディレクトリ サービスとは何か、また JNDI は Java プログラムからさまざまなサービスと均一に対話できるようにする Java API であることについて学びました。つまり、JNDI を利用すると、オブジェクトを特定の名前で JNDI ツリーに記録し、これらの同じオブジェクトを名前で受け取ることができます。ボーナス タスクとして、JNDI がどのように動作するかを示すサンプルを実行できます。他のオブジェクトをコンテキストにバインドし、このオブジェクトを名前で読み取ります。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION