8. JNDIアプリケーションの開発
JNDIを使用するためのクライアントの環境設定の方法やコーディングの仕方について説明します。
8.1. 環境設定
8.1.1. クラスパスの設定
スタンドアロンで動作するJNDIクライアントを動作させるためには、以下のjarファイルをJNDIクライアントのクラスパスに追加する必要があります。
クラスパスに追加するjarファイル一覧
JARファイル名 |
機能 |
${INSTALL_ROOT}/lib/gf-client.jar |
JNDIクライアント(*注1) |
(*注1)
WebアプリケーションやEJBアプリケーション、アプリケーションクライアントコンテナのようなコンテナ環境下で動作するコンポーネントの場合、クラスパスを設定する必要はありません。
8.1.2. 環境変数の設定
Linuxの場合、以下の環境変数の設定が必要となります。
環境変数の設定一覧
環境変数名 |
設定値 |
LD_LIBRARY_PATH |
/usr/lib |
8.1.3. 環境プロパティの設定方法
環境プロパティの設定方法には以下の種類があります。
- コンテキスト環境
java.util.Hashtableまたはそのサブクラスにプロパティ名と値を格納したオブジェクトです。
これを初期コンテキスト生成時のコンストラクタに指定します。初期コンテキストとはJNDIを
使用するために最初に取得する名前空間のコンテキストで、javax.naming.InitialContextクラス
のインスタンスです。
- Javaシステムプロパティ
以下のJNDI標準のプロパティに限りJavaのシステムプロパティで指定可能です。
- java.naming.factory.initial
- java.naming.factory.object
- java.naming.factory.state
- java.naming.factory.control
- java.naming.factory.url.pkgs
- java.naming.provider.url
- java.naming.dns.url
- リソース・ファイル
JNDIクライアント実行時のクラスパスにjndi.propertiesというファイルを作成し、
そのファイルにプロパティの設定を記述します。このファイルは、java.util.Propertiesクラス
で読み込み可能な形式で記述する必要があります。
なお、指定の優先順位は優先度の高い順から、コンテキスト環境、システムプロパティ、リソース・ファイルとなっています。また、以下に示すプロパティはコロンで区切ることにより複数の値を持つことができますが、これらのプロパティに対して複数の値が設定されている場合、優先順位の高いほうから連結された値になります。
- java.naming.factory.object
- java.naming.factory.url.pkgs
- java.naming.factory.state
また、WebOTX独自の環境プロパティとして以下のものが設定できます。
8.1.4. 環境プロパティの必須プロパティ
JNDIの環境プロパティには多くの指定可能なプロパティがありますが、通常JNDIクライアントで指定が必要なプロパティは以下の二つです。
8.2. プログラミング
8.2.1. 初期コンテキストの取得
JNDIを使用するためには、最初に初期コンテキストを得る必要があります。初期コンテキストはjavax.naming.InitialContextクラスのインスタンスを生成することで取得します。このとき、コンストラクタに環境プロパティを指定することにより、コンテキストの環境を指定することができます。
例1. デフォルトの環境プロパティを使用する場合
import javax.naming.InitialContext;
. . . .
InitialContext initCtx = new InitialContext();
例2. JNDIサーバのホスト名を指定する場合
import java.util.Hashtable;
import javax.naming.InitialContext;
. . . .
Hashtable env = new Hashtable();
env.put("java.naming.provider.url", "rmiiiop://jndihost");
InitialContext initCtx = new InitialContext(env);
8.2.2. 基本的な操作
Javaプログラム内でJNDIを使用してEJBやJDBCデータソースを得るためのコーディング例を示します。
-
EJBホームインタフェースのルックアップ
EJBの配備時に指定した名前をルックアップします。
例.
“MyBean”という名前で登録したEJBホームインタフェースを取得する。
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
. . . .
InitialContext initCtx = new InitialContext();
Object o = initCtx.lookup("MyBean");
MyBeanHome home = (MyBeanHome)
PortableRemoteObject.narrow(o, MyBeanHome.class);
-
JDBCデータソースのルックアップ
JDBCデータソースの定義時に指定した名前をルックアップします。
例.
“MyDataSource”という名前で定義したデータソースを取得する。
import javax.naming.InitialContext;
import javax.sql.DataSource;
. . . .
InitialContext initCtx = new InitialContext();
DataSource ds = (DataSource)
initCtx.lookup("MyDataSource");
8.2.3. Java EE コンポーネントで使用する名前空間
Java EE
仕様では、各コンポーネントはEJB、JDBCデータソース、JavaMailなどのさまざまなリソースをJNDI経由で取得します。その際、"java:comp/env"
という特殊なスキーマ名とコンテキスト名を持つ名前を参照します。この名前は各コンポーネント単位で独立した名前空間となります。つまり他のコンポーネントからはこの名前を参照することはできません。また
Java EE
コンテナ環境下で動作しないアプリケーションからこの名前の参照を試みるとjavax.naming.NamingExceptionが発生します。
この名前空間に名前をバインドするのは各コンポーネントの動作環境であるコンテナ(EJBコンテナ、Webコンテナ、アプリケーションクライアントコンテナ)のみです。各コンテナはそれぞれの配備記述子の定義に従ってバインドします。
例.
“MyDataSource”という名前でリソース参照されているJDBCデータソースを取得する。
import javax.naming.InitialContext;
import javax.sql.DataSource;
. . . .
InitialContext initCtx = new InitialContext();
DataSource ds =
(DataSource)initCtx.lookup("java:comp/env/MyDataSource");
8.3. 厳密な同期処理モード
JNDIサーバがレプリケーションされた環境では、JNDIサーバ同士の不整合を解消する目的で、同期処理が行われます。
同期はJNDI管理ツールからユーザが明示的に、あるいは、JNDIクライアントが新たなJNDIサーバを検出した場合に自動的に行われます。
既定の同期は、あるJNDIサーバが、他のJNDIサーバの持っている名前の中で、自サーバに持っていない名前を検出し、名前とそれに
関連付けられたオブジェクトを取り込む、という動作を行います。
そのため、もしネットワーク障害などが発生し、一時的に一部のJNDIサーバにJNDIクライアントがアクセスできない時間帯に、
すでにある名前に対してオブジェクトが再登録された場合、アクセス可能なJNDIサーバにのみ新しいオブジェクトが登録されます。
ネットワーク障害が解消したあとは、その名前に対するオブジェクトに不整合が発生しますが、
この不整合は検出できず、同期を実行しても解消されません。
すでにある名前に対するオブジェクトの再登録を行うようなJNDIクライアントアプリケーションの場合、この動作により、JNDIサーバと
通信障害が発生した後などに、参照処理のたびに違うオブジェクトが返却される、などの事象が発生する場合があります。
これが問題となる場合は、JNDIクライアントアプリケーションを厳密な同期処理モードで動作させてください。
厳密な同期処理モードでは、JNDIクライアントがJNDIサーバにオブジェクトを登録する際、メタデータとしてオブジェクト登録時刻と
オブジェクトのhashCode値も一緒に登録します。JNDIサーバに差異があるかどうかの判定には、従来の名前があるかどうかに加え、
同じ名前があれば、オブジェクトのhashCode値が同じかどうかチェックします。
もし、hashCode値に差異がある場合、オブジェクト登録時刻を見て、より登録時刻が新しい方のオブジェクトが"正しい"オブジェクトとして同期されます。
JNDIレプリケーション環境で、複数のJNDIクライアントが存在し、厳密な同期処理モードで動作する際、JNDIクライアントのシステムクロックにずれがあると、
古いオブジェクトで同期される可能性があります。
8.4. JNDI使用時の注意点
8.4.1. バインド対象のオブジェクトに関する注意
JNDIクライアントからJNDIサーバにオブジェクトをバインド、あるいはルックアップする際、必ず内部的にオブジェクトのシリアライズおよびデシリアライズが行われます。
よって、シリアライズの時間的コストが高いオブジェクトをバインド対象に使用すると、その分JNDIアクセスの時間的コストが高くなってしまい、性能劣化の原因となります。
シリアライズのコストが高いオブジェクトとは、参照が深くネストしているオブジェクトが典型的です。
すなわち、インスタンス変数に他インスタンスへの参照を持ち、参照先のインスタンスがさらに他のインスタンスへの参照を持ち… という構造を持ったクラスになります。
シリアライズの仕様上、ネストの底まで参照する全てのインスタンスをシリアライズする必要がありますので、コストが高くなってしまいます。
このような構造になりやすいクラスとしては、JDKの標準ライブラリ内ではjava.util.Collectionのサブクラス(java.util.Setおよびjava.util.Listのサブクラス)および、
java.util.Mapのサブクラスが挙げられます。これらインタフェースを実装したクラスをJNDIサーバへのバインド対象とする際には、ネストが深くならないように注意する必要があります。
自作クラスをバインド対象とする際も、深いネストにならないように注意してください。
また、シリアライズ時に呼ばれる、synchronized修飾されたwriteObjectメソッドを持つクラスは、
シリアライズが排他制御されるため、さらにコストが高くなります。JDKの標準ライブラリ内ではjava.util.Vectorおよびjava.util.Hashtable、java.lang.StringBufferが
該当します。これらクラスはなるべくJNDIサーバへのバインド対象にしないようにしてください。