CodeZine(コードジン)

特集ページ一覧

リフレクションAPIを使用して適応性の高い動的なコードを記述する

JAASのLoginContextクラスにおけるリフレクションAPIの実装

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/07/25 12:00

ダウンロード LoginContext.java (7.2 KB)

JavaのリフレクションAPIを使用すると、実行時の条件に順応するコードを記述できます。しかも、この長所は大規模なアプリケーションでなくても活用することができます。本稿では、リフレクションを使用してアプリケーションの堅牢性と柔軟性を高める方法について説明します。

はじめに

 開発者のコンピュータには、将来的なビジネスルールの変更やアプリケーションの実行時環境の変化を予測する水晶玉は付いていません。しかし、JavaのリフレクションAPIを使用すれば、コンパイル時に判明している情報のみに基づいてアプリケーションの能力を制限する必要はなくなり、アプリケーションを実行時環境の変化に動的に対応させることができます。

 初期設定ファイルやプロパティファイルを読み取るテクニックはよく知られていますが、リフレクションAPIを使い慣れている開発者はあまり多くありません。リフレクションを利用すると、コードの機能を実行時環境に合わせて大幅に拡張できます。つまり、ユーザー設定やサーバー設定、実行時変数などに基づいてプログラムの動作を変更することが可能です。本稿では、Java Authentication and Authorization Services(JAAS)LoginContextクラスがリフレクションAPIをどのように利用して柔軟性のあるセキュリティサービス層を実装しているかを解説します。このクラスは、ログインモジュールをアプリケーションの起動時にロードするのではなく、オンデマンドでロードします(JAASはJava 1.4のコアJDKの一部です。Java 1.3xをお使いの方は、JAASを個別にダウンロードできます)。

リフレクションの目的

 リフレクションAPIは「java.lang.reflect」パッケージに含まれています。このパッケージのJavadocをざっと見てみると、ConstructorFieldMethodといったJava言語の基本要素の名前を持つクラスがたくさんあることに気付くでしょう。これはリフレクションAPIの目的を示す第一のヒントです。つまり、このAPIはクラスの内部的な動作の「リフレクション」(reflection=反射、反映)を提供するのです。しかし、このAPIで実現できるのがクラスの要素のレポートを取得することだけであるならば、興味深くはあっても、動的なコードを記述するための役には立ちません。リフレクションAPIでは、クラスの構成要素を問い合わせるだけでなく、クラスを実行時に動的にロードおよびインスタンス化することができ、さらにそのクラスのオブジェクトのメソッドを呼び出すこともできます。

必要なバージョン
 Java 1.3以降(Sunによると1.3のサポートは終了しているそうなので、少なくとも1.4xをダウンロードすることをお勧めします)。

 リフレクションを使った設計で難しいのは、静的には実装できない(または実装しようとは思わない)サービスのうちどれを実行時に提供するかを判断することです。実行時環境に応じて異なる動作をする再使用可能なサービスを提供したい、というのがStrutsやJAASなどのフレームワークが作成される理由の1つですが、小さなアプリケーションでも、静的なソリューションより動的なソリューションの方が好ましい場面はよくあります。ただ注意しなければならないのは、リフレクションを多用しすぎると、ロードすべきクラスを判別するための負荷が大きくなってパフォーマンスが低下するおそれがあるという点です。

 以降を読む前に、ここで「javax.security.auth.login.LoginContext」のソースコードをコピーしてください。このソースコードは、SunのJavaソースコードのzipファイルに含まれています(ここからダウンロード)。また、独立したスタンドアロンファイルとしてここからもダウンロードできます。

JAASとリフレクションを使用してセキュリティ層を作成する

 セキュリティが必要とされる場面では、通常は、実行時にユーザーに対して識別情報とアクセス権限が関連付けられます。アプリケーションの開発者は、ユーザーの身元を資格情報に基づいて識別し、そのユーザーに対してどの操作を認めるかを判断する必要があります。JAASはこの問題を解決するために、プラグ可能なログインモジュールをサポートするセキュリティサービス層を提供しています。JAAS自身にはカスタムモジュールという概念がないので、JAASは実行時に状況に応じてログインモジュールを構築する必要があります。

 このログインモジュールは、ログインページ、スマートカードリーダー、指紋リーダーなど、どんな入力デバイスからのユーザー入力でも処理できます。JAASは、認証と認可の層をアプリケーションから切り離して抽象化し、それぞれのアプリケーションに最適な方法でセキュリティを処理できるようにします。アプリケーション開発者がJAASに対してしなければならないのは、設定ファイル内で複数のログインモジュールの名前を指定することだけです(もちろん、アプリケーションのクラスパスのどこかにコンパイル済みクラスを配置しておく必要があります)。実行時にユーザーが認証を行おうとすると、必要なログインモジュールが動的にロードされてインスタンス化され、適切なメソッドが呼び出されます。

 行番号を表示できるエディタ(Eclipseなど)を使用している場合は、そのエディタでSun提供のJavaソースコードを開いてみると、これから見ていくinvoke(String methodName)メソッドが619行目から始まっているはずです。先ほど述べたとおり、JAASはこのメソッドの中で、設定ファイルに指定されているモジュールの一覧を反復処理します。

1. for (int i = 0; i < moduleStack.length; i++) {

2.        try {

3.        int mIndex = 0;
4.        Method[ ] methods = null;

5.        if (moduleStack[i].module != null) {
6.            methods = moduleStack[i].module.getClass().getMethods();
7.        } else {

              // instantiate the LoginModule
8.            Class c = Class.forName
                    (moduleStack[i].entry.getLoginModuleName(),
                    true,
                    contextClassLoader);

 4行目でMethod型の配列を宣言し、これを6行目で初期化します。この配列に格納したメソッドを、後で状況に応じて呼び出します。8行目では、現在のmoduleStackエントリの名前で表されるクラスを探し、それを汎用のClass型オブジェクトとしてロードします。2つ目のブール型パラメータは、このクラスオブジェクトを初期化する必要があることを意味しています(初期化とインスタンス化はイコールでないことに注意してください。この時点では、静的変数とブロックが初期化されていますが、オブジェクトはまだ作成されていません)。3つ目のパラメータは、クラスをロードするために使用するjava.lang.Classloaderオブジェクトです。

 次のコードは、Constructorオブジェクトをインスタンス化する方法を示しています。LoginContextクラスの先頭で定義したPARAMS変数(1行目)と3行目で宣言したargs変数は、どちらも同じ目的に使用されます。このオブジェクト配列は、いずれもオブジェクトを作成するための引数として使用されます。2行目ではConstructorオブジェクトを作成するため、4行目ではLoginModuleの実際のインスタンスを作成するために使われます。今回の例では、どちらの場合も引数の配列が空ですが、必ずしもそうなるとは限りません。他のシナリオでは、リフレクションを使用しない場合のコンストラクタのシグネチャと同様に、コンストラクタ引数が配列内の引数に一致することもあります。

1.  private static final Class[ ] PARAMS = { };
   //Defined at the start of the class

2.  Constructor constructor = c.getConstructor(PARAMS);
3.  Object[] args = { };

   // allow any object to be a LoginModule
   // as long as it conforms to the interface
4.  moduleStack[i].module = constructor.newInstance(args);

5.  methods = moduleStack[i].module.getClass().getMethods();

 引数をClass型またはObject型の配列として扱うという考え方は、最初は奇妙に見えるかもしれません。しかし、どのクラスがインスタンス化されるかは実行時にならないとわからないので、このような「汎用化」の手法は不可欠です。Class型またはObject型を使用すれば、どんな型の引数でも効果的に表現することができます。Boolean.TYPEInteger.TYPEなどを使用すれば、プリミティブ型ですらこの方法で表現できます。4行目では、2行目で作成したConstructorオブジェクトを使用してLoginModuleのインスタンスを作成し、5行目では、このクラスのメソッドをMethod型の配列に格納しています。

 次はモジュールを初期化します。INIT_METHOD変数は、実行される実際のメソッド名を格納する静的変数です。したがって、Methods配列の中に目的のメソッドが含まれているかどうかをgetName()メソッドを呼び出して確認し(2行目)、含まれている場合はループを抜けて、引数を表すObject型の配列を初期化し(3行目)、その後で目的のメソッドを呼び出します(4行目)。

     // call the LoginModule's initialize method
1.  for (mIndex = 0; mIndex < methods.length; mIndex++) {
2.      if (methods[mIndex].getName().equals(INIT_METHOD))
     break;
     }

3.  Object[] initArgs = {subject, callbackHandler,
             state, moduleStack[i].entry.getOptions() };
    // invoke the LoginModule initialize method
4.  methods[mIndex].invoke(moduleStack[i].module, initArgs);

 この時点で、実行中のコードが、JAASの設定ファイルで指定されているどのLoginModuleクラスの初期化処理でも呼び出せるようになります。簡単に言えば、これがリフレクションの威力です。操作対象のクラスやオブジェクトをコード内で指定しなくても、実行時環境が自動的に判別してくれるのです。

 完全なソースコードリストを見ると、invoke()メソッドがtry-catchブロック内に書かれていることに気付くでしょう。リフレクションでは、作成されて動的にメソッド呼び出しが行われるクラスとそのオブジェクトが既に存在していることを前提にしています。そのようなクラスやメソッドが存在しないことが判明した場合、または要求されたアクションを実行するためのセキュリティアクセス権がない場合は、適切な例外がスローされます。

 JAASやStrutsのような堅牢なサービスフレームワークではリフレクションがよく使用されますが、大規模なアプリケーションやフレームワークをコーディングしなくても、リフレクションの長所を利用することができます。さまざまな種類のクラスやインターフェイスがあり、アプリケーションでどのサービスを提供すべきかが実行時にならないとわからない場合には、リフレクションを試してみる価値があります。



  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

あなたにオススメ

著者プロフィール

  • Doug Tillman(Doug Tillman)

    Grainger.comで活躍するベテランのJavaおよびPython開発者。開発者の役に立つツールを作成することに強い関心を持ち、積極的に取り組んでいる。

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5