IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Open source | Java technology  >

Apache Geronimoでの依存性注入 第1回: J2EEアプリケーションでの分離を新しい角度から見る

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 初級

Neal Ford (nealford@nealford.com), Application Architect, ThoughtWorks

2006年 2月 07日

この記事は、Apache GeronimoにおけるDI(dependency injection: 依存性注入)の動作を学ぶ2回シリーズの第1回です。ここでは最も軽量なDIコンテナーであるPicoContainerを使う例を通して、DIの理論的な基礎を、複雑な詳細を排除しながら解説して行きます。

ソフトウェア開発者は、常にコードの再利用を追求するものですが、それは当然なことです。その目的を達成するための手段は、Fortranでのファンクションからオブジェクト指向プログラミングに至る中で、また継承からインターフェースへと、時代と共に変わってきました。こうした発展の各段階において、固定的な依存関係からコードを分離するための手法として、それ以前の手法よりも優れた手法が発見されてきました。コード再利用を推進するために最も良いことは、インターフェースを実装から分離することです。Eric Raymondは『Art of UNIX Programming』の中で、UNIX®哲学の幾つかを次のように指摘しています。

  • モジュラー化の原則: 単純な部分を書き、きれいなインターフェースで接続する
  • 分離の原則: ポリシーを機構から分離し、インターフェースをエンジンから分離する
  • 表現の原則: ナレッジをデータの中に入れ込むことによって、プログラム・ロジックは愚かで堅牢となるようにする

こうした考え方は古いものですが、私達は、こうした考えをJava™技術で実現するための新しい方法を発見し続けているのです。そして分離のための技術の最新版、DI(dependency injection: 依存性注入)は、上記の理想を反映したものです。様々な場所で様々な方法で実現されている新たな概念と同様、この技術にも、その概念と実装との間に多くの混乱が起きています。ここではDI(IoC: Inversion of Controlとしても知られています)を解説し、次にDIがApache Geronimoの中でどのように実装されているかを説明します。

DI対IoC

DIフレームワークが混乱を呼ぶ原因の一つとして、DIを記述するための用語の問題があります。皆さんは、IoC(Inversion of Control)とDIが、ほとんど完全に交換可能なものとして聞き慣れているかも知れません。しかし両者の意味するところは、同じではないのです。

IoCは、大部分のフレームワークを網羅する、一般的な用語です。つまり制御要素を、通常ならば制御対象である要素(被制御要素)に反転することを意味するのです。別の言い方をすると、通常はフレームワークの中の制御する側であるものが、制御される側になるのです。例えばMVC(Model-View-Controller)設計パターンでは、コントローラーがメソッドを呼び出します。イベント駆動環境の中では、ビューがコードを、イベント・ハンドラーを通して呼び出します。従って、ビュー(つまり制御される側)が、コントローラーの役割を果たすことになります。

私は、IoCという用語は、意味が広すぎると思います。まるでJ2EE(Java 2 Platform, Enterprise Edition)開発を、ソフトウェア開発として一括りにするようなものです。確かにJ2EEはソフトウェア開発ですが、ソフトウェア開発の中でも非常に高度に特化されたものです。

幸い、Martin Fowlerが救いに登場しました。そして何人かのフレームワーク構築者と協力する中で、この分離スタイルを依存性注入と名付けたのです。この用語の方が、開発者が実際に行うことを具体的に記述しているため、IoCよりもずっと的確と言えます。従って、この記事と次回の記事では、このコーディング・スタイルを依存性注入と呼ぶことにします。もし皆さんがIoCという用語を使った他の資料を読む場合には、それを書いた人の意図を充分に確認する必要があります。




上に戻る


依存性注入

ソフトウェア・コンポーネントが、自分が動作するために他のコンポーネントと依存し合っている場合、それらは『結合されている』と言われます。ソフトウェア開発において、ある程度の結合は不可避ですが、最小限にとどめるべきです。例えば、ライブラリー・コードを構築する場合、タイプをインターフェースとして定義する方が、コンクリート・クラスとして定義するよりも適切です。こうしておけば、コンクリート・クラスを後から変更する場合でも、ライブラリー・コード(インターフェースの中に作られた定義のみに依存しています)を変更しなくても済みます。DIは、コンポーネント間での高度な結合を避けるための、もう一つの方法です。この方法では、あるコンポーネントの依存対象であるコンポーネントを、実行時にコンテナーが注入できるのです。一例として、図1の例を考えてみてください。


図1.単純で楽観的な、Customer persistenceに関するフレームワーク
図 1.  単純で楽観的な、Customer persistenceに関するフレームワーク

この例では、Customer WorkflowクラスはCustomer Persistenceクラスに、絶望的なほど強く結合されており、Customer Persistenceクラスが今度はデータベースに結合されています。『何だこれは!』とでも言いたくなるアーキテクチャーです。幸いJava世界の開発者は、こうした形式の高度な結合を避けることができます。実際、こうした形式の結合を避けることが、EJB(Enterprise JavaBeans)技術を構築するための大きな動機の一つだったのです。

この場合では、コンクリートCustomer Persistenceクラスをインターフェースとして表現すれば、幾らかのコード分離を実現することができます。図2は、同じ構造を、少し改善したものです。


図2.関係を分離するためにインターフェースを使う
図 2.  関係を分離するためにインターフェースを使う

依存関係をインターフェースに強制することによって、そのインターフェースの契約を実装した様々な実装クラスを提供することができます。この場合では、XML文書から読み取るCustomer Persistenceクラスと、リレーショナル・データベースを使う別のCustomer Persistenceクラスを持つことができます。どちらの機構が使われているか、ワークフロー・クラスは気にもせず、また知りもしません。

EJBへの分かれ道

この後に続く論理的な結論を早送りして先に進むと、インターフェースやファクトリー、プールされた(プロキシー)オブジェクトなどを使った、DIによるコード再利用に到達します。これはEJB技術として知られているものです。図3は、同じ関係を、典型的なEJB関係で表現したものです。


図3.インターフェースによる分離をEJBで実現したもの
図 3.  インターフェースによる分離をEJBで実現したもの

こうしたすべてが、本当に必要なのでしょうか。確かにEJB技術によって、あるレベルの分離が実現できますが、そのために必要な代償を考えてみてください。EJB仕様を書いた人達は、当時流行であったツール(つまり継承やインターフェース、そして設計パターンなど)を使ったのです。そして彼らは、開発者がエンタープライズ・アプリケーションを書く場合に直面しうる全ての問題を、このフレームワークの中に持ち込むことで解決しようとしたのです。オブジェクト・プーリングや自動トランザクション処理、セキュリティーその他、EJB技術が提供する素晴らしいものは、図3の中にありません。その理由は、私がそうした機能を求めなかったためであり、どれもEJBフレームワークには含まれているのです。つまり結果的に、私の抱える単純な分離問題が、巨大な問題になってしまうのです。

なぜこうした分離スタイルが反発を受けているのか、皆さんには理解できるでしょう。Bruce Tateがブログの中で的確に述べている通り、単純なアプリケーションを構築するために、「象一頭を食べるようなことはしたくない」のです。必要のない要素の実装を逐一強制するような手法は、うまく行きません。そうしたフレームワークには利点もありますが、同時に複雑さの度合いが桁違いに大きく、遅かれ早かれ利点が複雑さに圧倒されてしまうのです。




上に戻る


DIによる分離

使ってみれば分かることなのですが、EJB 2.0は、こうした問題の解決には不向きなのです。不必要に大げさに、また複雑にすることなく、アプリケーションを分離するための方法として、もっと単純な方法があるはずなのです。その方法として現在考えられているのが、DIなのです。実際、EJB 3は(Geronimoと同様)、クリーンな手法を優先して重量級のフレームワークを避けており、DIの手法を採用しています。

Geronimoや、GeronimoでのDI実装を見る前に、単純なコンテナーを見ることにしましょう。下記に挙げる例では、PicoContainerを使用しています。これはThoughtWorksが開発した、DIのみを行うオープンソースのコンテナーです。このコンテナーを見ると、分離における注入の動作を理解することができます。それによってGeronimoの動作も理解でき、それがこのシリーズの第2回にもつながって行きます。




上に戻る


コンストラクター注入とPicoContainer

DIの動作に関する主な考え方として、『コンストラクター注入(constructor injection)』と『セッター注入(setter injection)』という、2つがあります。コンストラクター注入は、戻すべきコンクリート・オブジェクトのタイプを、コンストラクターを使って決定します。セッター注入は、set() メソッドを通してタイプを注入します。

PicoContainerでは、コンストラクターを通して依存関係を注入します。例えば図2では、Workflowクラスは、パーシスタンス機構を使ってcustomerオブジェクトを作成する必要がありました。この例に関しては、2つの別々なファインダー・クラスがあります。つまり検索にXMLファイルを使用するFinderFromFileと、リレーショナル・データベースを使うFinderFromDbです。この例は非常に小さなものですが、幾つかのファイルを使っています。それらを要約したものが表1です。


表1. PicoContainerの例の中にあるファイル
ファイルタイプ目的
CustomerLister クラスあるファインダー・タイプを使って顧客を名前で検索するクラス
FinderFromFile クラスCustomerFinderインターフェースの実装であり、テキスト・ファイルの中にある顧客を検索します
CustomerFinder インターフェースCustomerオブジェクトを発見する何かに関する意味体系を定義するインターフェース
Customer クラスこの検索の対象(顧客情報をカプセル化しています)
CustomerWorkflow クラスアプリケーションに対するコントローラー・クラス(コンテナーを初期化します)
TestCustomerListing クラス顧客ファインダーを実行するユニット・テスト

図4は、これらのクラス間の関係を説明したものです。


図4. PicoContainerの例の中にあるクラス間の関係
図 4.  PicoContainerの例の中にあるクラス間の関係



上に戻る


コードについて

下記のコード・リストを見ると、PicoContainerの中でのDIの動作が分かるでしょう。

CustomerFinder

CustomerFinderインターフェースによって、DIが実現されます。DIが動作するためには、インターフェースが必要です(そのコンクリート・クラスの中に、求める振る舞いのコンシューマーを注入します)。この場合では、CustomerFinderインターフェースが、find(String name) というメソッドを定義します。これをリスト1に示します。


リスト1.  顧客の発見方法を定義するインターフェース
                
public interface CustomerFinder {
Customer find(String name);
}                           


このインターフェースは、顧客を発見するための意味体系を、実際の検索実行の詳細を露出することなく定義します。(実行の詳細は、XMLファイルを使用するかデータベースを使用するかによって異なります。

FinderFromFile

FinderFromFileコンクリート・クラスは、FinderFromFileインターフェースを実装します。このクラス(リスト2)は、フラット・ファイルの中から名前で顧客を見つけることに責任を持ちます。


リスト2.  フラット・ファイルに対するfind(String name) を実装するクラス
                
public class FinderFromFile implements CustomerFinder {
private String fileName;

public FinderFromFile(String fileName) {
this.fileName = fileName;
}
public Customer find(String name) {
// . . . details omitted
}
}


CustomerLister

次に、CustomerListerというファインダー・クラスを呼ぶことに責任を持つクラス(リスト3)を定義します。注入されるのは、このクラスの依存関係(つまり、どちらのCustomerFinderインターフェース実装を使うのか)なのです。


リスト3.  このクラスのファインダー振る舞いを、コンテナーが注入する
                
public class CustomerLister {
private CustomerFinder finder;

public CustomerLister(CustomerFinder finder) {
this.finder = finder;
}

public Customer findCustomerByName(String name) {
return finder.find(name);
}
}

リスト3を見ると、CustomerListerコンストラクターが、CustomerFinderインターフェースを実装するクラスのインスタンスを受け付けていることが分かると思います。コンテナーはこのインスタンスを、このCustomerListerに注入します。インスタンスがコンストラクターの一つにパス・スルーされるので、この振る舞いをDIの世界ではコンストラクター注入と呼びます。

もう一つの(そしてもっと広く使われている)振る舞い形式は、依存対象であるクラスがset() メソッドによって注入される、セッター注入です。PicoContainerは、両方の形式の注入をサポートしています。両者の実際の違いとしては、こうした依存対象クラスが無い場合に、明示的な依存関係を使ってクラスを構築することを許すかどうか、という点です。つまり、依存関係を注入できなければ依存対象クラスを構築できない、という理屈なのです。理屈を別にすれば、機能的には2つの注入形式の間に差はありません。

リスト4は、(コンストラクター注入ではなく)セッター注入が使われた場合に、CustomerFinderインターフェースがどんな風になるかを示しています。


リスト4.  セッター注入を使った場合のCustomerFinder
                
public class CustomerLister {
private CustomerFinder finder;

public CustomerLister() {
}

public void setFinder(CustomerFinder finder) {
this.finder = finder;
}
}

CustomerWorkflow

次は、CustomerWorkflowクラスです。このクラスは、この例ではコントローラーとして動作します。このクラスは、そのconfigureContainer() メソッドの中でPicoContainerをコンフィギュレーションします。次に、このメソッドがコンテナー(シングルトン(singleton)として動作します)を作成し、コンポーネントとそのパラメーターを登録します。CustomerWorkflowクラスをリスト5に示します。


リスト5.  この例でのコントローラー(CustomerWorkflowがコンテナーをコンフィギュレーションする)
                
public class CustomerWorkflow {

public MutablePicoContainer configureContainer() {
MutablePicoContainer pico = new DefaultPicoContainer();
Parameter[] finderParams =  
{new ConstantParameter("customerListing.xml")};
pico.registerComponentImplementation(CustomerFinder.class, 
FinderFromFile.class, finderParams);
pico.registerComponentImplementation(CustomerLister.class);
return pico;
}
}

PicoContainerをコンフィギュレーションすることによって、注入するコンポーネントに対するパラメーターを規定できることに注意してください。(Geronimoでは、こうしたコンポーネントはGBeansと呼ばれます。)またコンフィギュレーションが、XML文書の中ではなくJavaコードの中で行われることにも注意してください(Springコンテナーのように)。もしXMLの中で行いたい場合には、PicoContainerに似たオープンソースのコンテナーである、NanoContainerを使うことを検討してください。どちらを使うにせよ、これで注入に対する依存関係が設定できました。あとはこうした依存関係を必要とするコードを呼び、コンテナーに作業を行わせるだけです。

TestCustomerFinder

ジグソー・パズルの最後の一片は、このプロセスを駆動するクラスです。この例では、すべてが想定通り動作することを、ユニット・テストを使用して検証しています。TestCustomerListingクラス(リスト6に示します)によって、すべてが結合されます。


リスト6.  注入をデモするテスト・クラス
                
public class TestCustomerListing extends TestCase {
private CustomerWorkflow workflow;

public void setup() {
workflow = new CustomerWorkflow();
}

public void teardown() {
workflow = null;
}

public void testCustomerFinder() {
MutablePicoContainer pico = workflow.configureContainer();
CustomerLister lister = (CustomerLister) 
pico.getComponentInstance(CustomerLister.class);
Customer foundCustomer = 
lister.findCustomerByName("Homer");
assertEquals("Homer", foundCustomer.getName());
}
}

TestCustomerListingクラスは、setup() メソッドの中でWorkflowクラスのインスタンスを作り、次にconfigureContainer() メソッドを呼び出します。次にTestCustomerListingクラスはPicoContainerを使って、listerオブジェクトに対して正しい依存関係(この場合はFinderFromFileファインダー・クラス)を持つCustomerListerクラスを提供し、これよって、名前を指定された顧客への参照が取得されます。

この例には可動部分が幾つかありますが、この例を見れば、DIの持つ、分離の実力を見ることができると思います。顧客に対するパーシスタンスのために全く新しい方法が作られたことによって、単にインターフェースを拡張しコンテナーにコンフィギュレーション・コードを追加するだけで、相変わらず顧客を見つけられるのです。こうすることによって、単に言語の中に組み込まれたオブジェクト指向プログラミング原則を使っただけでは実現できなかった、あるレベルでの分離が得られるのです。




上に戻る


次回は

DIのような話題を理解することが難しいのは、DIの話題とは無関係な他の話題とDIとが、非常に強く結びついているためです。例えば、DIを学ぶためにGeronimoを学ぼうとしても、GeronimoにはDIとは何ら関係のない可動部分が大量にあるため、簡単には行きません。この記事では、DIの話題を実装から切り離し、最小のソフトウェア・スタックを選んでDIの特徴を検証しました。そしてDIを使用する動機とDIの定義を説明し、あるコンテナーによって、一つのコンポーネントから別のコンポーネントに依存関係を注入できることを示しました。

次回の第2回では、PicoContainerから離れ、Geronimoに移ります。そして、この記事の単純な例で示した原則が、J2EEアプリケーション・サーバーのように複雑なものにも同じように適用できることを示します。GeronimoではPicoContainerと同じレベルの分離が実現できますが、それだけではなく、より複雑な振る舞いを注入できるような、事前定義された一連のサービス・フックが用意されているのです。



参考文献

学ぶために

製品や技術を入手するために
  • PicoContainerをダウンロードして、試してみてください。

  • NanoContainerをダウンロードして、この記事で示した例を作成してみてください。NanoContainerは、PicoContainerを開発した人達の中の何人かによって書かれ、PicoContainerと関連していますが、考え方は少し異なり、そのままの形で利用できる機能がPicoContainerよりも数多く用意されています。

  • 皆さんの次期オープンソース開発プロジェクトを、IBM trial softwareを使って構築してください。ダウンロード、あるいはDVDで入手することができます。

  • Apache Geronimo, Version 1.0をダウンロードしてください。

  • IBM WebSphere® Application Server Community Edition V1.0の無料コピーをダウンロードしてください。これはApache Geronimoオープンソース技術の上に構築された軽量のJ2EEアプリケーション・サーバーであり、皆さんの開発作業やデプロイ作業を加速するために作られています。


議論するために


著者について

Neal Fordは、エンド・ツー・エンドでのソフトウェア開発と実現に特化した世界規模のITコンサルティング企業、ThoughtWorksのアプリケーション・アーキテクトです。著書には『Developing with Delphi: Object-Oriented Techniques, JBuilder 3 Unleashed』や『Art of Java Web Development』があります。彼は大規模なエンタープライズ・アプリケーションの構築を中心としたコンサルティング活動を行っています。連絡先は彼のWebサイト、 www.nealford.com、あるいはnford@thoughtworks.comです。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



はいいいえわからない
 


 


12345
不充分・不完全である大変素晴らしい
 


この記事を共有する

はてなブックマーク はてなブックマーク livedoorクリップ livedoorクリップ del.icio.us del.icio.us Buzzurl(バザール) Buzzurl(バザール) Choix! Choix!
Saafブックマーク Saafブックマーク FC2ブックマーク FC2ブックマーク MM/memo MM/memo ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
CZブックマーク CZブックマーク newsing newsing




上に戻る


    日本IBMについて プライバシー お問い合わせ