Eclipse 4 (e4) Tutorial Part 4- Dependency Injection Basics
By Jonas Helming
Eclipse 4 (e4) Tutorial Part 4- DIの基本
本チュートリアルの以前のパートでは、アプリケーションモデルを作成し、それを実装に結び付け、
さらに、拡張する方法を説明しました。
この連載はPDFでダウンロードできます。
これまで用意したコード例において、Eclipse4のさらなる革新機能である依存性注入(DI)を暗黙に利用してきました。DIは、チュートリアルの1章を捧げるのに十分な理由があるくらい、Eclipse4において中心的な役割を担っています。このチュートリアルでは次のようなことを説明します。
- どのオブジェクトが注入されることが可能か
- Eclipseコンテキストがどう動くか
- 注入を作用させるのにどのアノテーションが使えるのか
- どうやってEclipseコンテキストを拡張するのか
- どうやって注入を手動で引き起こすか
依存性注入?
Webプログラミングの世界では、依存性注入は、Google GuiceやSpringなどの著名なプロダクトにより、しばらくの間ホットな話題でした。
Eclipse4で、依存性注入は、Eclipseの世界に加わりました。それは、本質的には、あるオブジェクトが外部の他のオブジェクトをどうやってアクセス可能にするかについてです。親コンポジット、入力オブジェクト、ロガーのようなサービスを必要とするビューが、Eclipseでの例となるでしょう。
買い物リストを見ながら食料品の買い物をすることを現実世界との比較例にできるでしょう。買い物リスト上にはあなたが必要なすべてのものが書かれています。今、買い物を行うための3つの方法があります。
1番目の方法は、適切な店に行き、必要な物資を棚から取ることです。プログラミング世界では、これはシングルトンパターンによるアクセスに対応しますしかし、あなたは必要な物資をどこで買えばよいかを正確に知っている必要がありますさらに、店は、閉まる、もしくは、移転する可能性があります。
2番目の方法は、できるだけ買い物リストを網羅する既製の食糧箱を注文することです。その箱は、あなたの玄関に届けられ、それがどこから来たかを気にする必要はありません。しかし、あなたが欲しい数より多いか少なくなるかもしれません。何かが足りなければ、依然として店に出向く必要があります。2番目の方法は、フレームワークによって定義されたインターフェースを実装し、それによって満たすことに対応します。
3番目の方法は、仮の話になりますが、あなたが紙切れを冷蔵庫の上に掲げ、必要なものを正確に読み上げます。あなたが帰宅したとき、冷蔵庫は正しく読み上げた物資で満たされます。
正確に欲しい物だけを得られ、それがどこから来たかについては気にする必要はありません。
これは、現実世界では(今のところ)不可能ですが、依存性注入を通してEclipse4のプログラミング世界では可能なのです。基本的な考えは、クラス自身が外部からどのオブジェクトを欲しいのかを明確に述べることです。その後、フレームワークはそれらのオブジェクトを注入します。
クラスのフィールド、コンストラクタのパラメータ、もしくはフレームワークから呼ばれるメソッドのパラメータとして、オブジェクトは注入されます。
もっともシンプルなケースでは、必要なオブジェクトは、@Inject アノテーションによって、マークアップされます。さらに、振る舞いや注入タイミングを制御する多数のアノテーションがあります。
例えば、@Execute アノテーションは、ハンドラの実行時に呼ばれるメソッドをマークアップします。このメソッドに必要なパラメータが注入されます。
|
Eclipseコンテキスト
オブジェクトが注入されることは、直観的で実際に役立つように見えますが、注入されるオブジェクトが実際にどこから来るのか、どうやって個々のオブジェクトを識別するのかといった疑問が残されています。
つまり、フレームワークは、ある場所でどのオブジェクトが注入されるかをどうやって決めているのでしょうか?
Eclipse4では、Eclipseコンテキストと呼ばれるものがあります。これは、注入のために用意されたオブジェクトのリストの一種です。技術的には、このコンテキストは、文字列とオブジェクトのマップです。詳しくは説明しませんが、オブジェクトは、フルクラス名で、例えば、org.eclipse.swt.Compositeといった名前の下に保存されます。
さて、固有の型のオブジェクトが要求されたとき、適切なコンテキストが検索されます。もし、要求された型のオブジェクトがそのコンテキストに含まれているとすると、このオブジェクトは、コンストラクタ、またはメソッドを呼ぶ、あるいはフィールドを満たすために後で使われます。
Eclipseコンテキストを使った依存性注入
ところで、Eclipse4では、他にもグローバルなコンテキストがあるのでしょうか?さもなければ、注入する正しいオブジェクトを識別することは大変難しくなるでしょう。それゆえ、アプリケーションモデルのいくつかの要素は、独自のコンテキストを持っています。
例えば、ウインドウ、パースペクティブ、パーツなどです。これらは階層的にリンクされています。例えば、オブジェクトがパーツのコンテキストに見つからない場合、パースペクティブ、もしくはウインドウ、ワークベンチのコンテキスト、OSGiコンテキストが検索されます。OSGiコンテキストは、サービスなどアプリケーション全体に対して有効なオブジェクトを含んでいます。
Eclipseコンテキストは階層的にリンクされています。
一般にアプリケーションコンテキストモデルのすべての要素は、上位階層の方向に対してアクセスできます。なので、例えば、パーツのコンテキストでは、パーツを含んでいるウインドウが注入されます。
|
さらに、コンテキストは、パーツのコンポジットや、実行中アプリケーションのシェルなど、
アプリケーションモデルに関連するいくつかのSWT要素を含みます。Eclipse 4のサービスは、共通にアクセスされるオブジェクトも請け負います。
例えば、現在の選択、Eclipseワークベンチの設定などをコンテキスト内で利用することが可能です。ルートコンテキストは、すべてのOSGiサービスを含んでいます。大事なことを言い忘れていましたが、あなた独自の項目をコンテキストに挿入することも可能です。
@Named
もし、特定の型だけでなく、ある型の特定のインスタンスにアクセスしたい場合は、その注入に対して名前を指定することができます。これは、@Namedという追加アノテーションを通して行われ、@Injectと組み合わせて使われます。
@Named では、注入されるオブジェクトの名前を定義している文字列を指定することができます。このケースにおいて、コンテキストは、注入されるオブジェクトの型ではなく、対応する文字列に対して検索します。
仕様としては、@Namedが無い注入は単なるショートカットであり、そこでは、パラメータの型が注入のための変数名となることが仮定されています。
逆にいえば、名前が明示されずにコンテキスト内に収められたオブジェクトは、フルクラスパスをもって利用可能ということです。それゆえ、次の例において、@Namedは何の効果も持たず、省いても大丈夫です。
|
Eclipse4 サービスは、自動的に、あるID群に紐付いたいくつかのオブジェクトを含めます。IServiceConstantsインタフェースを見ると、正しい名前がわかります。例えば、このような感じでアクティブシェルが注入されます。
|
さらに、コンテキスト内にある名前でカスタムオブジェクトを置くこともできます。さらなる詳細は、本チュートリアルの次の章で触れようと思います。
@Optional
当然のことながら、コンテキストが、マッチした注入オブジェクトを含んでいないこともあり得ます。このような場合、Eclipse4の依存性注入機構はエラーを示します。さらに仕様的にいえば、例外が送出されます。
注入できないことによって、コンストラクタ、メソッド、フィールドで欠けてしまったパラメータを必要とするクラスは、正しく初期化されません。しかし、いくつかのパラメータ、例えば、特定のサービスのアクティブ選択などは、すべての場合で必要ではありません。
このようなパラメータに対しては、@Optionalアノテーションが使えます。@Optionalでマークされたオブジェクトがコンテキストで利用できない場合、nullが注入されます。
このケースでは、注入されるオブジェクトへのアクセスに先だって、nullチェックが行われなくてはなりません。
@Active
あるユースケースにおいては、アプリケーションモデルから導かれた要素の特定の型だけでなく、現在アクティブば項目にもアクセスする必要があります。@Activeアノテーションを付けると、現在のアクティブな要素、例えば、アクティブパーツへのアクセスのための要素が注入されます。
|
オブジェクトを注入すること
最も単純なケースでは、注入は、@Injectアノテーションを通して発生します。アノテーションは、各々のパラメータの前、メソッドの前、コンストラクタ、もしくはクラスフィールドの前に置かれます。
もし、メソッドやコンストラクタを@Injectでマークアップすると、すべてのパラメータに対して注入されます。@Namedによる追加情報が無い場合、注入するオブジェクトは、パラメータやフィールドのタイプに対応して探索されます。
注入の正確なタイミングを制御するための追加的なアノテーションがありますが、原理的には、@Injectと同様に振舞います。このチュートリアルの先の章では、この追加的なアノテーションについて説明していきます。
注入の順序は、大変重要です。たとえばビューを例に取ると、クラスが初期化される際に、コンストラクタとそのパラメータが最初に注入されます。引き続いてすぐに、関連するフィールドが注入されます。結果として、フィールドはコンストラクタ内でアクセスすることはできません。
メソッドのパラメータは、メソッドがフレームワークから呼び出される際に注入されます。@Injectでマークアップされたメソッドは、コンストラクタやフィールドの後にオブジェクトを初期化するためにも呼ばれます。その注入されたオブジェクトがコンテキスト内で変更された場合、再注入されます。そのため、注入される値が変化するとき、メソッドは再度呼び出されます。
コンストラクタ
コンストラクタは、オブジェクトの存在に欠かせないパラメータを含んでいるはずです。不要なパラメータは、オブジェクトのテスト容易性と再利用性を低下させます。
特に、オブジェクトの初期化はコンストラクタ呼び出しの後で呼ばれる分離されたメソッド群でなされるべきです。コンストラクタでの依存性注入の典型的な例は、このチュートリアルの以前の章で説明したように、ビューの親コンポジットを注入することです。
ビューはアプリケーションモデルのパーツの文脈で初期化されるので、この場合のコンポジット型の仕様は明らかであり、追加のアノテーションは必要ありません。
|
フィールド
クラスのコンストラクタ呼び出しの後にクラスフィールドが注入されます。典型的な使いどころは、クラス内でグローバルに利用可能となるサービスの注入です。 この一例は、ビューの現在の選択を設定するためのセレクションサービスです。この例のように、サービスは通常アプリケーションに1つしか存在しないので、型の指示としては十分です。
|
注入されるフィールドは、潜在的に再注入される可能性があるので、final宣言を 付けてはいけません。final宣言付きのフィールドは、コンストラクタおよびそこでの注入を通して明示的にセットされる必要があります。
メソッド
クラスを初期化する過程で、コンストラクタ、フィールドへの注入処理を終えた後に、すべての注釈つきメソッドが順に呼ばれます。これはパラメータを持たないメソッドに対しても適用されます。メソッドの注入されたパラメータの1つがコンテキスト内で変化すると、その新たなパラメータでメソッドが再度呼び出されます。 メソッドにおける注入の良い例は、ビューやハンドラで、頻繁に反応することになるカレント選択です。しかし、このケースでは、パラメータの型指定が不足しており、@Namedアノテーションでマークアップする必要があります。 例えば、アプリケーションが開始された時には、コンテキストで無選択状態であるので、次の例にあるように@Optionalも使います。 次の例において、注入は常時繰り返し行われ、選択状態が変化する時に、再度メソッドが呼び出されます。
|
まとめ
依存性注入は、シングルトンオブジェクト群とフレームワークのインタフェースに関する依存性を低下させます。オブジェクトは使用するパラメータもしくはサービスを厳密に定義します。これはテストへの対応も簡単にします。 追加的なアノテーションを使うと、たとえばパラメータの使用をオプション化するなど、より詳細な仕様をもって、オブジェクトを注入することができます。この連載の次のパートでは、依存性注入のより深い部分の説明に割きます。
例えば、注入のタイミングを手動で制御する方法や、コンテキストにカスタム要素を加える方法などです。
@Injectに加えて、@PreDestoryや@PostConstructなどの追加のアノテーションも網羅します。これらのアノテーションによって、特定のフレームワーククラス上で直接的な依存性を作らずに、あるメソッドが呼び出されるタイミングをフレームワークに教えることができるようになります。