1.はじめに
1.1 ハリウッドの原則
皆さんは、ソフトウェアの開発に関して、「ハリウッドの原則(the hollywood principle)」[資料-1]という言葉を聞いたことがありますか?
Don’t call us, we’ll call you.
文字通り訳せば、「電話はしないでくれ。こちらからするから。」です。なぜ、これを「ハリウッドの原則」というのか知りません。ハリウッドというのですから、映画会社が俳優に対して、「出演の売り込みはしないでくれ、必要ならこちらから電話するから。」というところから来ているのではないかと想像しています。
ソフトウェアの開発に関するDI(かってはIoCと呼ばれた、 *1)[資料-2]技術・手法を言い当てる言葉として、しばしば「ハリウッドの原則」を目にします。確かにDIは「“Don’t call us, we’ll call you.”」という技術なのです。逆に「Call us,we wo'nt call you.」という技術もあります。
注 *1:DI(Dependency Injection), IoC(Inversion of Control) 意味は本文を参照して下さい。
前置きが長くなりましたが、この解説はDIを使ってコンポーネント化されたwebアプリを開発できるようになることを目標にしています。ただ、具体的なDI用のソフトウェア(DIコンテナ、例spring[資料-3])を使うところまでは行っていません。これは別に解説する予定です。その前段としてこの解説は次のことを狙っています。
端的にいうとハリウッドの原則を体で理解することです。「“Don’t call us, we’ll call you.”」のusとは誰で、youとは誰でしょうか?callとは何を意味するのでしょうか。
1.2 対象の読者
この解説は次の人を対象にしています。
構造の解説ですので言語には直接関係しませんが、例題の言語はJavaを使います。
Javaでwebアプリを開発する場合に共通に使われるJSPとサーブレットについて必要な範囲で知っていることを前提にしています。必要な範囲でというのは、この解説が対象にしている方の立場で違ってきます。
簡単なwebアプリを書ける方は、JSPとサーブレットを一応使えることを前提にしています。設計者は、JSPとサーブレットがwebアプリの中でどんな役割をするものかを理解している必要があります。工程管理者は、深くは必要ありませんが、設計者と似た知識が必要です。
2.webアプリの構造とコンテナ
2.1 「MVC + 3層」構造のwebアプリ
「MVC+3層」構造の3層構造は企業の基幹システムのプログラム構造として推奨されている構造です。図2-1は3層構造の図です。
図2-1 3層構造
層構造というのは、隣合う層だけが関連を持ち、隣合わない層は関連しないということです。単に3クラス(群)に分かれているというだけでは層構造とはいえません。
現実のシステムでは、3層構造といいながら、隣り合わない層が関連する構造になっている場合が少なくありません。
注 設計パターンの観点でいえば、層構造は「delegate」の構造でたとえば、デコレータパターンやブリッジパターン、ストラテジーパターンとして解釈できます。この解説は設計パターンの抽象的な説明はできるだけ避けて、実際にどうするのかについて説明します。興味のある方は[資料-4]を参考にして下さい。TODO 資料に確認
次に、「MVC構造+3層」のMVCは設計パターンの一つとして有名なです。本来はGUIを実装する構造ですが、webアプリの構造として使われています。
その元々のクラス図を図2-2に示します。
図2-2 一般のMVCパターンのクラス図
webアプリの構造として、MVCと3層を組み合わせた構造が使われます。これを、図2-3に示します。
図2-3 MVC+3層構造
一般のMVCの場合と違って、webアプリのMVCではビューがモデルに直接作用することはありません。また個々のビュー(具体的な画面)は、モデル(のデータ)全体に比べてごく一部を使う場合がほとんどです。そのため、ビューとモデルの結合を整合する必要があります。このクラスをアクションと呼びます。アクションクラスを加えた、実際に使われるwebアプリのMVCのクラス図を図2-4に示します。アクションはコントローラから見るとモデルに見え、業務ロジックから見ると表示層に見えます。
図2-4 現実のMVC2+ 3層構造
図2-4の中の線で示されているオブジェクトの関係が簡潔であることが特徴です。
2.2 コンテナ
実際の実装では各クラスはコンテナの助けで動きます。図2-5の角丸の四角形はコンテナを意味しています。その中にあるクラスはコンテナによって起動され、直接又は間接にコンテナを参照して目的の処理をします。
図2-5 コンテナとMVC+3層構造
J2EEの仕様ではアクションとビュー、コントローラはwebコンテナの中で動き、業務ロジックとデータアクセスはEJBコンテナの中で動くことになっています。ただし、簡単なシステムでは業務ロジックとデータアクセスはコンテナを使わないで動かすことがあります。
なお、EJBは欠点が指摘されており[資料-5]、EJBコンテナに代わる軽量のコンテナが使われるようになっています。このコンテナをDIコンテナと呼んでいます。ただし、DIコンテナはEJBコンテナの代替として使われますが、利用できる範囲はそれに限ったものではありません。
設計で大切なことは、「アクションとビュー、コントローラ」と「業務ロジックとデータアクセス」を明確に分離することです。
言い換えると、業務ロジックとデータアクセスがwebコンテナを使わなくても動かなければならいということです。この部分はwebの環境で使われることを直接意識しなくて済むようにしなければなりません。たとえば、バッチ環境で動くプログラムから、業務ロジックとデータアクセスを使えるようにすることが必要です。
このことは実際の開発にあたって、業務ロジックだけをテストできることを意味し、3層で構造化することの利点の一つです。
注 DIコンテナは業務ロジックとデータアクセス専用ではありませんのでwebコンテナと協働することがあります。この場合も業務ロジックとデータアクセスがwebコンテナを直接使わない構造にすることが大切です。
EJBコンテナ/DIコンテナは必ずしも必要ありません。しかし、コンテナを使わないと設計時に意図した構造が製造時(コーディング時)に壊れるという問題が発生しがちです。コンテナは実行時にアプリのクラスの動きを支援するだけでなく、開発時に特定の構造をプログラマに強制する役割もあります。
構造が崩れた例を図2-6に示します。
図2-6 層化されない MVC2構造
図2-6に示すように、アクションの中に業務ロジックとデータアクセスが入り込み、分離できなくなっています。クラス(群)は構造化(層化)されないで複雑に関係しあい、webコンテナを直接参照するコードになりがちです。こうなるとMVC構造が生かされなくなります。残念ながらこの例はたくさん見かけます。
この解説の例題ではwebコンテナとしてTOMCATを使います。EJBコンテナに相当するコンテナは使いません。コンテナを使わなくても注意深くコーディングすると構造化(層化)できます。ただ、初心者を含む実際の開発の現場で、プログラマ全員に注意深いコーディングを期待するのは無理です。したがって、例題を離れて現実的なwebアプリを実装するには、何らかのコンテナを使うのが望ましいと考えます。次の章で説明するコンポーネント化を現実的なwebアプリに適用するにはコンテナが必須でしょう。
3.コンポーネント化
構造として、MVC+3層構造を前提に実装を考えます。
構造と並んで重要なことはコンポーネント化です。
この解説では業務ロジックやデータアクセスなどMVC+3層構造の構成要素の内容は説明しません。構成要素の間のインタフェースをどのように実装するかを説明します。
3.1 コンポーネント化とは
コンポーネントというのはソースを変更しないで使える部品(クラス群)のことです。コンポーネント化はクラス(群)を差し替えできるように(pluggableに)することです。図3.1-1と図3.1-2はコンポーネントの考え方を示す図です。
図3.1-1 コンポーネント化(1)
図3.1-1は、DBMS用のデータアクセスとXML用のデータアクセスが業務ロジックと結合できることを示しています。DBMS用のデータアクセスからXML用のデータアクセスに、又はその逆に変えるのに業務ロジックのソースは変更しないで済みます。
図3.1-2 コンポーネント化(2)
図3.1-2は異なった業務ロジックに同じDBMSデータアクセスを使えることを意味しています。業務ロジック1で使ったDBMSデータアクセスがソースを変更しないで業務ロジック2にも利用できます。
3.2 なぜコンポーネント化するのか?
コンポーネント化は再利用性を高める技術の一つです。
図3.1-1の例では、再利用性が生きるのは「業務ロジック」です。「データアクセス」ではありません。
もし、業務ロジックとデータアクセスが一体として作られていると、データアクセス部がDBMSの場合とXMLの場合の二つの「業務ロジック+データアクセス」を作らなければなりません。業務ロジックが論理的には変わらなくも二つの場合についてそれぞれ作る必要があります。図3.1-1の方式が実装できると業務ロジックは一つで済みます。
図3.1-2の例では再利用性が生きるのは「データアクセス」です。この場合は、一つのデータアクセスで二つ(以上の)業務ロジックに対応できます。
再利用性を高くすることで過去の開発又は他人(他社)の開発が「財産」として生きてきます。
さらに、コンポーネント化には再利用性以外の効用があります。
図3.1-1の例では、データベースとしてDBMSとXMLに対応できるプログラムを開発する場合、該当部分をコンポーネント化できると明らかに開発と保守が楽になります。
その結果、品質があがり、工程を短縮し、開発費を低減できます。機能の強化もしやすくなります。
3.3 どうすればコンポーネントになるか?
コンポーネント化するには何が必要でしょうか?
以下では、説明を簡潔にするため業務ロジックとデータアクセスとの関連についてだけ取り上げます。
3.3.1 直接具現化+直接参照
まず通常のクラスの参照の仕方を図3.3-1に示します。
図3.3-1 直接具現化+直接参照
業務ロジックの中にクラスをその名前で直接使うコードが含まれています。具体的な例を次に示します。
|
注 この説明の中でクラス名の最後にImpが付くクラスは具体的なクラスを意味します。
この方式には次の二つの問題があります。
この方法を使うとデータアクセスのクラス名が変わると業務ロジックで参照できなくなります。
3.3.2 間接具現化+間接参照
これを避けるために抽象ファクトリパターンを使います。図3.3-2にこの方法を示します。
図3.3-2 間接具現化+インタフェース参照
抽象ファクトリパターンを使った場合のオブジェクトを参照する手順は次の通りです。
オブジェクトの参照手順 ① ファクトリに目的のオブジェクトの参照を要求します。 ② 該当クラスを具現化します。(注*1) ③ ファクトリから参照が戻ります。 ④ 参照を使って目的のオブジェクトに作用します。
注 *1:初回の要求時又は起動時に1度だけ具現化したものを戻します。
具体的なコーディングの例を次に示します。
|
注: IExampleDaoはinterfaceを意味します。実装を持つ具体クラスではありません。
この方法はファクトリを実装する必要があります。ただ、TOMCAT(一般にはサーブレットコンテナ)はファクトリと同等の機能を提供していますので、ここではファクトリの詳細は述べません。
3.3.3 JNDI+インタフェース
Java言語にはオブジェクト(の参照)を取り出すインタフェースとして、JNDIが準備されています。TOMCATでもこれを実装しています。この方法を図3.3-3に示します。
図3.3-3 JNDI + インタフェース参照
JNDIではリポジトリ(オブジェクトの倉庫)に置かれたオブジェクトをキーで取り出すことが出来ます。リポジトリがちょうどファクトリの役割をします。
TOMCATの設定パラメータでリポジトリに置くオブジェクトとそのキーを指定します。この方法でオブジェクトを利用する手順は前項の手順のファクトリの代わりにリポジトリを使う点が違うだけです。
オブジェクトの参照手順 ① リポジトリに目的のオブジェクトの参照を要求します。 ② 該当クラスを具現化します。(注*1) ③ リポジトリから参照が戻ります。 ④ 参照を使って目的のオブジェクトに作用します。
注*1:TOMCATではリポジトリへの要求時に1度だけ該当クラスを具現化しているようです。
参照を要求するメソッドはJavaで提供されています(JNDI)。
具体的なコーディングの例を次に示します。
|
TOMCATの設定の例は次の通りです。
|
具体的には例題の中で説明します。ここでは設定の内容を理解してください。
クラス名を使わないで参照する仕組みを作ることができました。
JNDIを使う方法はオブジェクト(の参照)を取り出す処理を業務ロジックに書かねばならないことが欠点です。本来の業務ロジックやデータアクセスに関係のないコードが入り込みます。リポジトリの登録のキーも本来のロジックの中に出てきます。
JNDIを使う方法のもう一つの問題は、業務ロジックの中でJNDIを使うと、業務ロジックを動かすときにTOMCATが必要になることです。業務ロジックとデータアクセスを表示層から分離する原則に反します。これでは、MVC+3層構造とはいえなくなります。
この問題はJNDIとファクトリを組み合わせて回避できます。JNDIをファクトリの中だけで使うことにするのです。ファクトリの具体的な処理は隠蔽されますので、もしバッチ処理で動かそうとする場合はバッチ処理独自のファクトリを書けば業務ロジックとデータアクセスを変更しなくてもバッチ処理で動かせます。
3.3.3 DI+インタフェース
DIではJNDIと違って、目的のオブジェクトの参照を要求する必要がありません。DIを使った方法を図3.3-4に示します。
図3.3-4 DI + インタフェース参照
手順は次の通りです。
オブジェクトの参照手順 ① 初期化時に該当クラスを具現化します。 ② 初期化時に該当クラスの参照を設定します。 (例ではデータアクセスの参照を業務ロジックに設定します) ③参照を使って目的のオブジェクトに作用します。
注 *1:初回の要求時だけで2度目からは初回に具現化したものを戻します。
初期化時に必要なパラメータを与える方法として次の方法があります。
DIをサポートした製品(オープンソース)では、パラメータを与える仕組みを提供していますので、webアプリの開発者はその仕組みを作る必要はありません。
参照を設定されるクラスには、その参照を外部のクラスから設定するメソッド(setter)が必要です。
例を次に示します。
|
パラメータの例を次に示します。これはspringのパラメータの場合です。
|
ExampleDaoImpとExampleServiceImpの二つを具現化して、service1にはdao1をdaoに設定する指定をしています。
3.4 ハリウッドの原則のレビュー
ここまでの説明でハリウッドの原則、
Don’t call us, we’ll call you.
がDIの本質を表していることを理解できましたか?
「us」とはDIコンテナのこと、「you」とはその中で動くアプリのクラスのことでしょう。
JNDIはどうでしょうか?
Call us,we wo'nt call you.
がJNDIの本質を表しといえます。
DIはかってIoCと呼ばれていました。時間的に古くからあるJNDIからDIを見ると、確かにIoCと言える思います。
4.クラス構造
具体的な例を示すために、「MVC+3層構造」のクラスの構造をさらに具体化していきます。
4.1 2要求モデル
具体的なwebアプリの使い方(操作法)は複雑です。簡単にするため、その全体を、意味を失わない程度に小さく分けた部分を取り出し、次の操作を考えます。全体はこれらの操作を組み合わせて遂行されていると考えるのです。
要求1
応答1
要求2
応答2
例えば次の場合があります。
要求1:昨日の売上要求
応答1:検索条件(支店名、担当者など)入力画面
要求2:検索条件を入力して、検索結果を要求
応答2:検索結果を表示
目的としている業務を小さく分割したときの、業務的に意味を失わない一部という意味でこれをジョブステップと呼ぶことにします。ジョブステップのシーケンスを図4.1-1に示します。
図 4.1-1ジョブステップのシーケンス
2要求からなるジョブステップのモデルを2要求モデルと呼ぶことにします。2要求モデルに基づいたプログラム(の一部)のテンプレートを2要求テンプレートと呼ぶことにします。
複雑な現実のwebアプリの操作の全体を2要求モデルで説明できるとは限りませんが、この解説の中でwebアプリの構造を考える上で役立ちますのでこのモデルを使います。
4.2 2要求モデルの処理シーケンスとクラス
2要求モデルの実装について考えます。
2要求モデルをMVC+3層構造で処理する場合のシーケンス図を図4.2-1に示します。
図4.2-1 2要求モデルのシーケンス
これに関係しているクラスの関係を図にしたものが図4.2-2です。図4.2-2ではクラス名に実装に使う名前に近いものを使っています。図4.2-2上でのクラス名を図4.1-1上に( )を付けて記しています。
注 トランザクションの処理はこの解説の趣旨ではないので略しています。実際のアプリでは必要です。コンテナの助けで対応できます。
図4.2-2 2要求モデルテンプレートのクラス図
この図の中のクラスの概要は次の通りです。
# | クラス | 概 要 |
1 | Controller | コントローラ。これは通常webアプリのフレームワーク(例struts)で提供されますので、開発する必要はありません。コントローラに与えるパラメータは必要です。 |
2 | JSP_n | 表示画面を作るオブジェクト。JSPで作成します。 |
3 | Action_n | 要求の処理をするモデル。アクションクラスと呼びます。 |
4 | Service | 業務処理を受け持つクラス。一般には一つのクラスとは限りませんが、ここでは一つのオブジェクトとして描いています。 |
5 | Dao | データアクセスを受け持つクラス |
6 | ActionForm_n | 入力データを保持するクラス |
7 | Dto_n | 画面の作成に使うデータもしくはデータベースのデータを保持するクラス |
8 | DbAccessor | 3層の中ではDaoに含まれる。Daoを分割した場合、データへの実際のアクセスを行うクラス。 |
9 | ConnectionBuilder | DBのコネクションのオープン/クローズ |
注 1:#6 ActionForm_nと#7Dto_nはシーケンス図に現れないクラスです。これらは、入力やデータベースのデータの運搬を担います。
2:クラス名に_nが付いているクラスはそれに相当するクラスが複数個あることを意味します。ここの例題では要求に対応して1個存在し、都合2個存在します。
シーケンスの中でDaoと記したクラスはクラスの構成図の中で次の3クラス(群)に分けています。
この解説は3層の各々の層のクラスの作り方を解説していません。ただ、Daoについては一般の場合も概ねこの3層(下部層)に分割されます。 該当部分を取り出すと図4.2-3になります。
図4.2-3 Daoの構成クラス
DaoはオブジェクトをDBのレコードに、又はその逆方向に翻訳するクラスです。sqlをここで組み立てます。
DbAccessorは実際にDBにアクセスするクラスです。
ConnectionBuilderはDBをアクセスするのに必要なコネクションのオープンとクローズを受け持ちます。コネクションのプーリングを行う場合はこのクラスで行います。この部分は通常データソースと呼んでいます。外部とのインタフェースは、Javaが標準で提供するDataSourceインタフェースを使います。この解説中ではインタフェースとクラスが混乱するのを防ぐため、クラスはConnectionBuilderと呼んでいます。
DBMSのベンダーがデータソースの部分を提供している場合があります。また汎用で使えるものがOSSとして入手できます。TOMCATにも汎用のものが付いています。
DaoとDbAccessorの部分もOSSとして提供されています。hibernateはその代表的なOSSです。この場合は、hibernate と業務ロジックを仲介するだけの簡単なDaoを作るだけでデータアクセス部を構成できます。hibernateはこの解説の対象外ですので詳しくは触れません。
Serviceのクラス(群)は実際のwebアプリでは複雑です。これは業務ロジックに依存して変わりますのでデータアクセスのような一般的な形を提示できません。例題では単純なクラスとして、Daoを呼び出しているだけのServiceクラスを使っています。
5.例題
5.1 概要
解説にしたがって次の例を示します。
各例題の構造を補足します。
図 5.1-1は直接具現化+直接参照の構造です。
図 5.1-1 直接具現化+直接参照を使ったwebアプリの構成
ActionはService.getInstance()でServiceの参照を取得します。Serviceは、自身の具現化時にDaoを具現化しそれをフィールドに保存します。Dao、DbAcessorも同じ方法で必要な参照を自身のフィールドに設定します。ServiceとDaoのinterfaceを実装する形を使っています。しかし、直接具現化+直接参照の方式では、interfaceは積極的には役立ってはいません。他の例題と統一するためにこの例題でもinterfaceを使っているだけです。
図5.1-2はJNDI+ファクトリの構造です。
図5.1-2 JNDI+ファクトリを使ったwebアプリの構成
この例題では、TOMCATがServiceからConnectionBuilderまでのクラスを具現化をします。Factoryはそれをstaticブロックで取得し保持します。ActionからDbAccessorクラスは必要な参照を、staticなメソッドを使ってFactoryに問い合わせて取得します。
JNDI(TOMCAT)以外で直接、具体クラスを使っている処理はなくなります。
図5.1-3はDIの構造です。
図5.1-3 DIを使ったwebアプリの構成
InitializerクラスがServiceからDataSourceまでのクラスを具現化します。そのクラスに関する情報はpropertiesから取得します。この例題ではpropertiesは外部のpropertiesファイルを使っています。
DIを使った場合の特徴は、各クラスから必要な参照を取得する処理がないということです。図5.1-2と比較するとこのことがよく分かります。
Actionが使うServiceの参照はInitializerの実行時には設定できません。図5.1-2と同じ方法で解決できます。しかし、この例題では違った方法を使っています。図5.1-4はその方法です。
図5.1-4 ActionへServiceの参照を設定する方法
この方法は次の通りです。
(1) 起動時
InitializerはServiceの参照をサーブレットコンテキストに保存します。
(2) Action実行時
Actionの本来の処理の前に、サーブレットコンテキストからServiceの参照を取得する処理を入れます。
これは、親(ベース)のクラスを作って、そこで行うと通常のActionクラスでは、DIと同じ効果でserviceを参照できます。
listenerはtomcatからwebアプリの起動時に呼び出され、その延長でInitializerが呼び出されます。listenerとInitializerを一体にすることも出来ます。
例題の動かし方は付録で説明します。
注:例題のソース等すべての資料は解説を補足するためだけに書かれたものです。実際のアプリにはそのままで使える内容ではありません。
各例題の共通のソースは次のものです。
共通のクラス
(1)JSP1 [v1_a01Example.jsp]
(2)JSP2 [v2_a01Example.jsp]
(3)ActionForm1 [Example1ActionForm]
(4)Dto1 [Example1Dto]
(5)ActionForm2 [Example2ActionForm]
(6)Dto2 [Example2Dto]
(7)ConnectionBuilder[ConnectionBuilder]
ユーティリティ
ユーティリティとして次のクラスを使っています。
(1)SetCharsetFilter: webの画面からの文字のエンコーディングを設定し、文字化けを防ぐ。
(2)DbUtil:sqlの組み立てを容易にするためのユーティリティ
(3)ExampleContextListener:例題3でinitializerを起動するためのリスナー。プログラム起動時に一度だけ実行されます。
アクションクラス
アクションクラスは、内容は同じだが継承する親のクラスが違うのでクラス名だけ異っています。
(1)Action1
(a)N01Example1Action
(b)N02Example1Action
(c)N03Example1Action
(2)Action2
(a)N01Example2Action
(b)N02Example2Action
(c)N03Example2Action
これらは、実際の開発であれば、一つのクラスにまとめるべきものです。例題では実装法を示すためにまったく同じ処理を重複して書いています。
以上のクラスにはコンポネント化固有の箇所はありません。これらの説明は略します。
次に例題によって異なる部分を概説します。
なお、例示したコードの中で、クラス(タイプ)名の先頭がIのクラスはinterfaceを意味します。
5.2 BaseAction
各例題ごとに異なるBaseActionがあります。各ベースアクションは業務ロジック(Serviceクラス)の参照の設定の仕方が違っています。
(1)例題1:N01ExampleBaseAction
|
Serviceクラスのスタティックなgetterメソッドを使ってserviceの参照を取得します。
(2)例題2:N02ExampleBaseAction
|
ファクトリのスタティックなgetterメソッドを使ってserviceの参照を取得します。
(3)例題3:N03ExampleBaseAction
|
サーブレットコンテキストから参照を取り出します。アクションについては複雑さを避けるためDIの手法は使っていません。springはアクションでもDIで参照を使える仕組みがあります。
5.3 Service
Serviceクラス自身の具現化の仕方とDaoの参照の設定の仕方が各実装で違います。
(1)例題1:N01ExampleService
|
シングルトンとして自らを具現化する処理とstaticメソッドでその参照を戻す処理があります。
Dao(N01ExampleDao)をそのクラス名で直接参照して具現化しています。
(2)例題2:N02ExampleService
Serviceクラス自身を具現化する処理はありません。該当する処理はTOMCATが行います。
Daoを参照するメソッドの中で次の処理があります。
|
(3)例題3:[N03ExampleService]
|
Serviceクラス自身を具現化する処理はありません。この処理はイニシャライザー (N03ExampleInitializer)で行われます。
setDaoが定義されています。Initializerがこのメソッドを呼び出してdaoを設定します。Daoを参照するメソッドの中にdaoを取り出す処理はありません。
5.4 Dao
(1)例題1:N01ExampleDao
|
DbAccessorをそのクラス名を直接参照して具現化しています。DbAccessorもシングルトンとして動作しますが、そのための特別の処理はありません。シングルトンのServiceがDaoを具現化することを前提に作られています。
ドライバのクラス名、dbのurl、ユーザ名、パスワードを設定しています。クラス名だけでなくこれらが変わるとプログラムソースを変更する必要があります。
(2)例題2:N02ExampleDao
Daoクラス自身を具現化する処理はありません。該当する処理はTOMCATが行います。DbAccessorを参照するメソッドの中で次の処理があります。
|
(3)例題3:N03ExampleDao
|
setDbAccessorが定義されていて、Initializerがこのメソッドを呼び出してdbAccessorを設定します。
5.5 DbAccessor
(1)例題1:DbAccessor
|
ConnectionBuilderをそのクラス名を直接参照して具現化しています。これもDaoと同じようにシングルトンとして動きますがそのための処理はありません。ServiceからDao、DaoからDbAccessorを具現化することを前提に作られています。
setDbParamsメソッドがあります。これはDaoから呼び出されます。dataSource(connectionBuilder)にドライバーのクラス名、データベースのurl、ユーザ名、パスワードを設定します。
(2)例題2:DbAccessorJndi
|
ConnectionBuilder(データソース)を具現化する処理はありません。該当する処理はTOMCATが行います。
(3)例題3:DbAccessorDi
|
Daoクラス自身を具現化する処理はありません。この処理はイニシャライザー (N03ExampleInitializer)で行われます。
setDataSourceが定義されていて、Initializerがこのメソッドを呼び出してdataSource(connectionBuilder)を設定します。
5.6 ファクトリ
これは例題2(N02Example)にだけ必要です。
ここで、JNDIを介してリポジトリにアクセスして必要な参照を取り出し、それをstaticなメソッドで取り出せるようにします。各クラスにJNDIを使った処理を入れるより簡潔になります。
JNDIを設定するデータをcontext.xml(contextの部分は変わります)に書き込みます。
ファクトリの主要部分を次に示します。
|
staticブロックでJNDIからServiceの参照を取り出します。その参照を戻すstaticなgetterが定義されています。他のコンポーネントについても同じです。
context.xmlのResourceの定義(抜粋)は次の通りです。
|
最初のResourceはService用です。次のものはdataSource(connectionBuilder)用のものです。
dataSource用のResource要素にはクラスの参照だけでなく、プロパティも一緒に設定されています。例えばurlです。プロパティはJava Beanの命名則に従わなければなりません。たとえば、プロパティurlは名前がsetUrlのメソッド(setter)が該当クラスに定義されていなければなりません。
この命名則に従うと、該当クラスを具現化するときにそのプロパティに値を設定できます。
例題1の場合は、たとえばurlの値はDaoからDbAccessorを経由してdataSourceに設定される方式になっていました。このため、DBMSを変えるためにはDaoのソースを変えなければなりません。
JNDIを使うとこの処理をwebアプリに書く必要はありません。DBMSが変えるときはResourceの設定内容を変更すれば対応できます。
注:現実のwebアプリではDBMSに依存する処理がsqlを作るDaoにもある場合があります。各DBMSがそれ固有のsqlの文法を含むからです。これの依存性を下げる工夫は別途必要です。
5.7 イニシャライザ
これは例題3(N03Example)にだけ必要です。例題の実装ではパラメータは外部のプロパティファイルから取り出しています。パラメータの供給方法はJNDIを使うなどいくつか考えられます。実際のDIコンテナでは、この部分も提供されるので開発する必要はありません。例題はDIの初期化の処理の大筋を理解する目的で作っていますので、DIコンテナを使わないで実装しています。n03Example.propertiesがプロパティファイルです。
|
プロパティファイルからプロパティの値(完全クラス名)を取り出します。instantiateメソッド(イニシャライザー内のメソッド)を使ってクラスを動的に具現化します。Serviceの参照はサーブレットコンテキストに設定されます。Daoの参照は、DIの約束通りsetDaoを使ってservice(Serviceのインスタンス)に設定されます。
Serviceの参照がサーブレットコンテキストに設定されるのはActionクラスから参照されるからです。MVCのフレームワークとしてstrutsを使っていますので、Actionクラスの具現化と参照の管理はwebアプリの開発者に隠れています。そのため、Actionクラスの中でサーブレットコンテキストをアクセスしてServiceの参照を取り出す処理を書いています。これはDIの手法ではありません。
ただし、この処理は親のクラスで行っています。実際のActionクラスを作る人はあらかじめServiceの参照が設定されているとして、つまり、DIを使う場合と同じようにコードを書けます。
実装に依存するプロパティはsetterを呼び出して設定できません。というのは、具現化されたインスタンスの参照のタイプがinterfaceでなければなりません。そのタイプが実装クラスだと実装クラスに依存してしまうからです。参照に対応するinterfaceには実装に依存するプロパティのsetterは定義されていません。ですから、実装に依存するプロパティはsetterで設定できません
このため、実装に依存するプロパティはJavaのreflectionを使って設定します。setPropertyByNameがそのためのメソッドです。
5.8 コンポーネントの入れ替え
実際にコンポーネントを入れ替えて見ます。それぞれの実装方式でどんな変更をしなければならないか考えて見ます。
対象のコンポーネントは、DataSourceとします。これまではConnectionBuilderを使っていましたが、TOMCATに付いているDBCPを使います。
(1)例題1 N01Example
DbAccessorを書き換えます。
書き換え後のDbAccessorがダウンロードしたファイルのdev/resources/DbAccessor.javaです。
ConnectionBuilderとDBCPの両方を残さなければならないような、書き換えが出来ない状況であればたくさんの修正が入ります。実際に名前を修正しないでやってみて下さい。
(2) 例題2 N02Example
context.xmlのresource設定を変更するだけでDBCPに切り替わります。Resourceでtypeにjavax.sql.DataSourceを指定してfactoryを指定しないで置くとTOMCATの標準値である、org.apache.tomcat.dbcp.dbcp.BasicDataSourceがDataSourceとして使われます。
ソースは変更する必要がありません。
|
この設定は${context}.xmlに含まれていますが、コメントアウトされています。TODO
(3) 例題3 N03Example
n03Example.propertiesのdataSourceの値を変更するだけでDBCPに切り替わります。
ソースは変更する必要がありません。
|
この設定はn03Example.propertiesに含まれていますが、コメントアウトされています。
6.参考資料
(1)http://en.wikipedia.org/wiki/Hollywood_Principle
(2)Martin Fowler. Inversion of Control Containers and the Dependency Injection pattern,
http://www.martinfowler.com/articles/injection.html,2004
(3)Rod Johnson expert one-on-one J2EE Development without EJB,
pp1-12,2004,wiley publishinghttp://java.sun.com/blueprints/patterns/MVC.html
(4)Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides.Design Patterns
Elements of Reusable Object-Oriented Software,pp20-21,1994,Addison-Wesley
(5)Martin Fowler. Patterns of Enterprise Application Architecture,
p100,2003, Addison-Wesley,
(引用)
you don't need EJB to build a good J2EE application, despite what EJB
vendors tell you. You can do a great deal with POJOs(plain old Java
objects) and JDBC
(引用終り)
付 録
A.例題のための準備
例題を実習しない方はこの章を読む必要はありません。
例題を動かすために次のソフトウェアが必要です。
(1) eclipse
(2) TOMCAT
(3) struts
簡単なwebアプリなら書ける方を対象にしていますので、eclipseとTOMCATについては説明を省きます。開発環境はeclipseでなくて別の開発環境でもいいです。
strutsについては、ダウンロード後の利用の仕方が、この解説固有の部分がありますので説明します。
2.1 strutsのダウンロード
ダウンロードのサイト (2007.5.17現在)
http://struts.apache.org/download.cgi
struts-1.3.8-all.zip
なお、struts-2.0.6はこの解説では使いません。
A.2 strutsのサンプルを動かす
strutsの確認のためです。他の方法で確認できている場合はこの作業はしなくて済みます。
(1)struts-1.3.8を適当なディレクトリの中で解凍します。
(2)この中の ...\struts-1.3.8\apps\struts-blank-1.3.8.war をtomcatのwebappsディレクトリにコピーします。
(3)tomcatを起動します。
(4)struts-blank-1.3.8.warが実行されていることを確認します。
webブラウザから、次のurlを入力します。
http://localhost:8080/struts-blank-1.3.8/
その結果、「Welcome!」のページが現れるはずです。
(5)tomcatを停止します。
(6)webappsディレクトリ下のstruts-blank-1.3.8.warを削除します。
(7)tomcatのwebappsディレクトリに次の構造が出来ます。
例題は、struts-blankを元に作成しました。ただし、以降の説明と直接関係ありません。
A.3 例題をダウンロードする。
TODO リンク [例題 ダウンロード] --- Wordの場合はtutwebapp.zipとして保存
解凍します。
A.4 テスト用のディレクトリを作成する。
(1)テストに使うディレクトリを作ります。tutwebappとします。
(2)tutwebappの下にダウンロードして解凍したファイル群(ディレクトリを含む)をコピーします。
結果として次の構造になります。
|
ソースは次の通りです。パッケージごとに示します。
(1)jp.co.nsp_ltd.a01cmn
Example1ActionForm.java
Example1Dto.java
Example2ActionForm.java
Example2Dto.java
IExampleDao.java
IExampleService.java
(2)jp.co.nsp_ltd.a01data
IDbAccessor.java
(3)jp.co.nsp_ltd.a01data.impl
ConnectionBuilder.java
DbAccessor.java
DbAccessorDi.java
DbAccessorJndi.java
(4)jp.co.nsp_ltd.a0101
N01Example1Action.java
N01Example2Action.java
N01ExampleBaseAction.java
N01ExampleDao.java
N01ExampleService.java
(5)jp.co.nsp_ltd.a0102
N02Example1Action.java
N02Example2Action.java
N02ExampleBaseAction.java
N02ExampleDao.java
N02ExampleFactory.java
N02ExampleService.java
(6)jp.co.nsp_ltd.a0103
n03Example.properties
N03Example1Action.java
N03Example2Action.java
N03ExampleBaseAction.java
N03ExampleDao.java
N03ExampleInitializer.java
N03ExampleService.java
(7)jp.co.nsp_ltd.util
DbUtil.java
ExampleContextListener.java
SetCharsetFilter.java
なお、この資料の中ではtutwebappのパスを${webproject}で表します。
A.6 libにjarをコピー
解凍したstrutsのlibディレクトリから次の8個のjarファイルをtutwebapp/WEB-INF/libにコピーします。
# | ファイル名 |
1 | antlr-2.7.2.jar |
2 | commons-beanutils-1.7.0.jar |
3 | commons-chain-1.1.jar |
4 | commons-digester-1.8.jar |
5 | commons-logging-1.0.4.jar |
6 | commons-validator-1.3.1.jar |
7 | oro-2.0.8.jar |
8 | struts-core-1.3.8.jar |
A.7 新しいtaglibのインストール
struts1.3.8に含まれているjstl.jarとstandard.jarは版が1.02で旧いので、新しい版を次からダウンロードします。
http://jakarta.apache.org/site/downloads/downloads_taglibs-standard.cgi
1.1.2.zip
これを解凍します。中に含まれている次の2ファイルを${webproject}/web/WEB-INF/libにコピーする。
# | ファイル名 |
1 | jstl.jar |
2 | standard.jar |
A.8 webディレクトリをtomcatに登録
エディタを立ち上げ次の内容を入力します。
|
このひな型はダウンロードしたファイルのdev/resources/tutwebappbase.xmlです。次の設定は環境に合わせて変更する必要があります。
(1)docBase="C:/@/projEclipse/osss/tutwebapp/web"
(2)workDir="C:/@/projEclipse/osss/tutwebapp/web/work"
(3)Dbq=C:/@/projEclipse/osss/struts-1.3.8/db/textdb (2か所)
Resourceは例題2に使うための設定です。DataSource用のResourceは二つ設定し、片方はコメントアウトしています。この設定を変更すると実行時に使用するDataSourceを切り替えられることを示すためです。
これを
${tomcat}\conf\Catalina\localhostに適当な名前を付けて、xmlファイルとして保存します。このファイル名(のベース部)はurlの一部になります。ここでは、このファイル名をtutwebappbase.xmlとします。ファイル名(のベース部、例ではtutwebappbase)をこの資料では${contextbase}で表します
A.9 eclipseにwebアプリのテスト用のプロジェクトを作成する。
(1)プロジェクトのタイプはJavaプロジェクトにします。
(2)プロジェクト名はtutwebappprojとします。
(3)既存のソースからプロジェクトを生成を選択して、ディレクトリとして${webproject}を指定します。
(4)「次へ」を押します。
(5)ソースとしては${webproject}/web/WEB-INF/src/javaを指定します。
多分そうなっていますが、そうでない場合は設定して下さい。
(6)${webproject}/web/WEB-INF/classesをコンパイル結果の出力先に指定します。
多分、tutwebappproj/binが設定されていますから変更します。
(7)「完了」を押します。
この時点では未解決参照があるため、作成したプロジェクトはエラーがある状態になっているはずです。
未解決参照を解決するため次の操作をします。
(1)プロジェクトの先頭(tutwebappproj)を選択して、file/propertiesをクリックします。
(2)階層表示からJava Build Pathを選択、右側のタブからsourceタブをクリック。
ここに${webproject}/web/WEB-INF/src/java以外のものがJava Build Pathに登録されている場合はそれを削除します。 ${webproject}/web/WEB-INF/src/javaが登録されていない場合は登録します。
(3)librariesタブをクリック
(4)Add External Jarsクリックし、${tomcat}/common/libから次のjarを選択しbuild pathに追加します。
# | ファイル名 |
1 | servlet-api.jar |
2 | naming-factory-dbcp.jar |
これで未解決は解決されるはずです。エラーがないことを確認して下さい。
A.10 確認
A.10.1 htmlでHello World
webブラウザにHello World!と表示してみます。
${webproject}/web/htmlディレクトリにhelloworld.htmlのファイルがあります。
注:webで扱うファイルの名前は文字の大小も含めて同じにする必要があります。
url
http://localhost:8080/ ${contextbase}/html/ helloworld.html
を入力します。
hello worldと表示されるはずです。
A.10.2 JSPでHello World
jspを確認するため簡単なテストをします。
テスト
tomcatを経由してしかテストできません。
http://localhost:8080/ ${contextbase}/pages/ helloworld.jsp
Hello Worldと表示されるはずです。
A.10.3 JSPの機能を使ってHello World
先の例はhtmlの拡張子をjspに変更しただけですので、内容はjsp固有の機能は含まれていませんでした。
この例題では、この解説例題に共通に使うJSPの機能を設定して、Hello Worldを表示してみます。taglib、css、javascriptのテストを含んでいます。
http://localhost:8080/ ${contextbase}/pages/ helloworld2.jsp
Hello Worldと表示されるはずです。
A.11 漢字を入力できるようにする。
これまでの設定で漢字の表示は出来ます。しかし、漢字を入力した場合は「文字化け」します。これを避けるには、サーバに送られてきたデータで使っている文字コードの種類をJavaに明示的に設定しなければなりません。strutsを使っている場合は、ここで示すサーブレットのフィルターの機能を使います。
|
(ダウンロードしたweb.xmlにはすでに設定されています)
SetCharsetFilterクラスはダウンロードしたファイルに含まれています。これは例題だけのために作ったものです。実際のwebアプリに使うには改善する必要があります。
確認の方法
http://localhost:8080/ ${contextbase}/pages/confirmKanji.jsp
漢字を含む文字列を入力してそれが正しく表示されると正常に動いています。
A.12 コンテキストリスナーを設定する。
DIを使った例題ではプログラムの起動時にだけ実行される処理を書く必要があります。例題ではこれはコンテキストリスナーを使って実装しています。コンテキストリスナーはwebアプリが起動されるときに一度だけ呼び出されます。
web.xmlに次のパラメータを設定します。
|
(ダウンロードしたweb.xmlにはすでに設定されています)
jp.co.nsp_ltd.util.ExampleContextListenernが存在しないと、webアプリの起動時にエラーになり、webアプリが立ち上がりません。
ExampleContextListenernにDI用の初期化クラス(Initializer)の初期化メソッド(initProgram)を呼び出す処理を書いています。
確認の方法
http://localhost:8080/ ${contextbase}/pages/confirmContextListener.jsp
confirmContextListener.jspを実行して、画面に説明している内容が表示されると正常に動いています。
A.13 テスト用データベース
データベースの内容として次のwebページのデータを利用させていただきます。
'http://www.stat.go.jp/data/nihon/zuhyou/n0200200.xls
都道府県別人口と人口増加率
textファイルをjdbc-odbcブリッジを使ってdbとして扱います。実際のwebアプリで利用できるDBMSではありません。ただし、別のdbの本格的なDBMSに簡単に切り替えできます。
textファイルはダウンロードした資料のdb/testdbディレクトリの中に含まれているtable7.txtとschema.iniがdbの役割をします。
A.14 クラスに設定されたデータベースのurlを変更
N01ExampleDao(パッケージjp.co.nsp_ltd.a0101)にデータベースのurlの一部(dbq、テキストファイルを含むディレクトリ)が設定されています。これを環境に合わせて書き換えます。
以上