「SpringによるWebアプリケーションスーパーサンプル」という本でDIの概念を理解したことがある。
第1章しか読んでいなかったけど。DIの概念を理解するためには十分だった。
当時には業務でStrutsを使っていてSpringよりもStrutsの理解を深めるのが先決であった。
第2章からは後で読んでみようと思っていながら、今まで封印の状態に。この本に悪いことをしたと反省している。
少し話が長くなったが、この本ではDIの概念を説明するために三つのサンプルを用意している。
一つ目は依存関係を使って、「Hello World!」を出力する。
二つ目はinterfaceを使って、「Hello World!」を出力する。
そして、最後の三つ目はDIを使って、「Hello World!」を出力する。
結果的には「Hello World!」を出力するだけだが、なぜinterfaceを使って結局DIを使うようになったのか理解した。
サンプルコードだけではなく補足でUMLを描いたのも結構よかったと思う。
今度はその三つのサンプルを通してDIを理解してみよう。
依存関係を使った例
まずspring legacy projectを生成する。
作り方については「springの再入門 - eclipseでスタート」を参考にする。
HelloAppクラスからMessageBeanクラスのsayHelloメソッドを呼び出す簡単なサンプルだ。
まずはUMLから。
Amaterasというpluginで描いた。
UMLからjavaのコードを生成することは勿論javaファイルをマウスでドラグ&ドロップするだけでクラス図を作ってくれる優れものだ。
無料でtutorialが要らないぐらい使いやすいし、大体のモデリング機能を提供している。
昔から有名なツールなので、使用方法あちこちにある。
詳細はぐぐって参考にしていただきたい。
「クラス図の上からマウス右ボタンクリック>JAVA>エクスポート」順に選択する。
src/main/javaディレクトリの配下にクラスファイルの雛形が生成される。
ここで「依存関係」は何なのかちゃんと理解しないといけない。
HelloAppにてMessageBeanのインスタンスを生成してsayHelloメソッドを呼び出す。
MessageBeanというクラスがないとできないことだから、HelloAppクラスはMessageBeanクラスに依存しているという。
クラスとクラスの間の関係で関連と依存が紛れやすい。
依存は関連より弱い関係で、関連はインスタンスを使うクラスが属性としてそのインスタンスのクラスを持っている場合のことだ。
依存は使いたいインスタンスのクラスを属性として持っていないが、使っている場合である。(属性としては定義せずにメソッド内でインスタンスを生成して使う場合)
UMLで生成したクラスを下記のように修正する。
package practice.basic.hello1;
public class HelloApp {
public static void main(String[] args){
MessageBean bean = new MessageBean();
bean.sayHello("Spring");
}
}
package practice.basic.hello1;
public class MessageBean {
public void sayHello(String name){
System.out.println("Hello, " + name);
}
}
「HelloApp.javaファイルを選択してマウス右ボタンクリック>Run As>Java Application」順に選択して結果を確認する。
Hello, Spring
interfaceを使った例
一つ目のサンプルは問題がある。
MeesageBeanの内容が大幅に変更されたりすると、それを利用する側(MessageBeanに依存される側でここではHelloApp)も修正が必要になる。
サンプルが簡単すぎて実感できないかもしれないが、複雑な業務用のシステムであれば関係を持ってる全てのクラスに影響を与えてしまう。
このような影響を最小限にするためinterfaceが存在する。
interfaceに定義されているメソッドはそのinterfaceを具現するすべてのクラスで実装されることを保証する。
interfaceは具現したクラスの詳細は隠してinterfaceで提供されるメソッド名だけがそのクラスを利用するための入り口になる。
interfaceは規約を決めたものとも言える。
規約に従ってコーディングをすれば、変更があってもそれを利用する側の修正を最小限にすることができる。
二つ目のサンプルでは挨拶を英語だけではなく日本語でも出力したい。
sayHelloメソッドを持つinterfaceを作ってそのinterfaceで英語と日本語で具現したクラスをそれぞれ作成する。
まずUMLから。
一つ目のサンプルと同様にUMLからクラスの雛形を生成する。
UMLで生成したクラスを下記のように修正する。
package practice.basic.hello2;
public class HelloApp {
public static void main(String[] args){
MessageBean bean = new MessageBeanJa();
bean.sayHello("Spring");
}
}
package practice.basic.hello2;
public interface MessageBean {
public void sayHello(String name);
}
package practice.basic.hello2;
public class MessageBeanEn implements MessageBean {
public void sayHello(String name){
System.out.println("Hello, " + name);
}
}
package practice.basic.hello2;
public class MessageBeanJa implements MessageBean {
public void sayHello(String name){
System.out.println("こんにちは、" + name);
}
}
「HelloApp.javaファイルを選択してマウス右ボタンクリック>Run As>Java Application」順に選択して結果を確認する。
こんにちは, Spring
英語で挨拶したい場合はHelloApp.javaを下記のように修正すればいい。
- MessageBean bean = new MessageBeanJa();
+ MessageBean bean = new MessageBeanEn();
勿論この場合も利用する側の修正は必要になるが、その修正を最小限にすることができた。
このように共通的なinterfaceを作って置けば、必要に応じてそのinterfaceを具現するクラスを作ればいい。
既存のクラスには、手を出さなくてもいい。
そして、interface自体は変更されていないため、そのinterfaceを使っているクラスも修正を最小限で収められる。
DIを使った例
JAVAでBeanクラスを設計するときは、二つ目のサンプルのようにinterfaceを用いるのが基本だ。
これに加えて使いたいインスタンスを先に用意して必要なときにそのインスタンスを求める側に注入して使うのがDI(Dependency Injection)というものだ。
Beanのインスタンスが使われるときは、Spring Frameworkが代わりに依存関係を注入してくれる。
なんだかんだすべて自動でSpring Frameworkが依存関係を注入してくれるわけではない。
必要とされるBeanインスタンスが何なのかSpring Frameworkに知らせる必要がある。
そのために必要なのがBeanの設定ファイルだ。
Beanの設定ファイルは別途に生成しなければならない。
下記のようにBeanの設定ファイルを生成する。
practice/src/main/resourcesの配下にbeans.xmlを作る。
practiceの直下ではない。
そして、使いたいBeanのインスタンス情報を記入する。
<bean id="messsage" class="practice.basic.hello3.MessageBeanJa" />
これでidがmessageというMessageBeanインスタンスが用意された。
このBeanインスタンスを使ってみよう。
次はBeanFactoryでbeanインスタンスを取得する例である。
XmlBeanFactoryがdeprecatedになっているが、動作には問題ない。
package practice.basic.hello3;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
public class HelloApp {
public static void main(String[] args) {
// TODO Auto-generated method stub
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("src/main/resources/beans.xml"));
MessageBean bean = factory.getBean("message", MessageBean.class);
bean.sayHello("Spring");
}
}
それでもdeprecatedになっているのを使うのはよくないので、代わりにClassPathXmlApplicationContextで修正する。
package practice.basic.hello3;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloApp {
public static void main(String[] args) {
// TODO Auto-generated method stub
BeanFactory factory = new ClassPathXmlApplicationContext("beans.xml");
MessageBean bean = factory.getBean("message", MessageBean.class);
bean.sayHello("Spring");
}
}
BeanFactoryにいくつか機能を追加したのがApplicationContextである。
普段はBeanFactoryではなくApplicationContextを使ったほうがいいだろう。
Spring Framework内でbeanのインスタンスはBeanFactoryが生成するものなのでわざとBeanFactoryを入れてみた。
factory patternというデザインパターンを適用して作ったので名前がBeanFactoryである。
いきなりApplicationContextを入れることよりBeanFactoryを入れたほうが少しは分かりやすかったではないだろうか。
package practice.basic.hello3;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloApp {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
MessageBean bean = app.getBean("message", MessageBean.class);
bean.sayHello("Spring");
}
}
MessageBeanやMessageBeanJa、MessageBeanEnは二つ目のサンプルのものをそのまま利用する。
二つ目のサンプルと比較してみると、三つ目のサンプルのほうがもっと難しく見える。
しかし、大きな違いがある。二つ目のサンプルでは日本語で挨拶するためにHelloAppの修正が必要だった。
三つ目のサンプルではbeanの設定ファイル(beans.xml)だけ修正すればいい。
javaファイルには手を出さなくてもいいのだ。
参考
- SpringによるWebアプリケーションスーパーサンプル
- EclipseではじまるJavaフレームワーク入門