この記事について
この記事は 2011年に自分のブログに書いていた記事を発掘したものです。10年以上の文章なので、表現が稚拙だったり、時代が古いところもありますが、何かの役に立つかもしれないと考え、Qiitaに持ってくることにしました。
はじめに
Javaの開発をしている人は大概サーバサイドで何か作ってるんじゃないかと思いますが、このサーバサイドJavaを取り巻く環境は日々めまぐるしく進歩していて、ちょっと気を抜くとすぐに置いて行かれてしまいます。
特にサーバサイドJavaを取り巻く技術として「DI (Dependency Injection)」という単語は良く聞くと思いますが、正直僕はあまり突っ込んで使っていませんでした。DIの思想自体は理解しているつもりで、メリットも分かっているつもりではいましたが、「DIを積極的に導入した場合にどういうものが出来上がるのか」について、自分の中でもやもやとした状態が続いていたからです。
そのもやもやを晴らそうとドキュメントベースで知識は入れていたものの、DIコンテナの一つである Spring3 のマニュアルとか700ページ以上もあるんですよね。脳内のバッファがパンクしてしまいます。
習うより慣れろ、ということで簡単なアプリケーションを作ってみました。
DIの良さを体感しつつ実際の利用イメージを掴みやすくするために、MyBatisというDBアクセスフレームワークを使い、実際にDBを更新するものにしています。あ、DIコンテナはSpring 3.0.5 RELEASEを使っています。
作ろうとしているもの
作ろうとしているアプリケーションは次のようなものです。
- カードを管理するアプリケーション
- カードはCardクラスで表現され、idと nameという属性がある。
- DBにはカードを格納する為のCARDテーブルがある。
Cardクラスの定義は次のような感じです。
package jp.ohnaka.springmybatis.model;
public class Card {
private String id_;
private String name_;
public String getId() {
return id_;
}
public void setId(String id) {
id_ = id;
}
public String getName() {
return name_;
}
public void setName(String name) {
this.name_ = name;
}
}
これを格納する為のCARDテーブルのスキーマは以下のようにしました。
CREATE TABLE `CARD` (
`ID` varchar(64) PRIMARY KEY,
`NAME` varchar(128)
);
普通に作った場合
このカード管理アプリを普通に作った場合、mainメソッドはこんな感じになるとおもいます。
public class Main {
public static void main(String[] args) {
// CardServiceのインスタンスを取得
CardService service = new CardService();
// サービスの呼び出し
Card card = service.createCard();
System.out.println("Card was created: id=" + card.getId());
System.out.println("Num of cards: " + service.getNumOfCards());
card.setName("ohnaka");
service.updateCard(card);
Card updatedCard = service.getCard(card.getId());
System.out.println("Card was updated: id=" + updatedCard.getId() + " name=" + updatedCard.getName());
//
Card card2 = service.createCard();
System.out.println("Card was created: id=" + card.getId());
System.out.println("Num of cards: " + service.getNumOfCards());
//
service.deleteCard(card2.getId());
System.out.println("Card was deleted: id=" + card2.getId());
System.out.println("Num of cards: " + service.getNumOfCards());
}
}
CardServiceの実装は省略していますが、中身はどんなかんじになるでしょうか。もし10年前のJava技術で作ったら、getCard()や updateCard()などの各メソッドの実装には、「JDBCドライバの初期化をして Connectionを取得して、createStatementして、executeメソッドでSQLを発行する」ようなコードが書かれている事でしょう。
ここで、「new CardService()」としているところに注目してください。「インスタンスが必要な時に newする」というのはJavaでは当たり前の操作ですが、 DIでは「極力newせずに、インスタンスの生成はDIコンテナにおねがいする」ようにします。
そうすると何が嬉しいのかはあとで説明しますので、今は「DIコンテナを使うとnewを使わなくなる」と思ってください。ただ、newを使った場合よりもめんどくさくなりますので覚悟してください。
え?
そうなんです。ちょっぴりめんどくさくなります。
DIの説明で「newを使わなくて良くなる」と言う人がいますが、初心者にはなぜそれが「良い」のかわかりません。インスタンスを作る方法としてnewより簡単な方法なんて無いんですから。
なので僕は「DIコンテナを使うとnewを使えなくなる」と言うようにしたいとおもいます。
つまり、「本当は newみたいに手軽にやりたいんだけど、(Javaの文法の中で)DIのメリットを享受する為に仕方なく別の方法でインスタンスを作る」のだと思ってください。
大丈夫です。慣れればそんなにめんどくさく無いです(笑)。
Spring最初の一歩
DIコンテナであるSpringを使いたい訳ですが、まずはSpringの jarを本家サイトから入手してください。ダウンロードする為に名前を入れさせたりするのがちょっとうざいですね、、。
Mavenに慣れている人ならば、以下のようなpomを用意すれば良いでしょう。このpomにはMyBatisやらMySQLやらの設定も書いてありますが気にせずに。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jp.ohnaka</groupId>
<artifactId>spring-mybatis-test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring and MyBatis test</name>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.0.4</version>
</dependency>
<!-- MyBatis-Spring adapter -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.0.0</version>
</dependency>
<!-- DB -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Java6の文法を使いたいので maven-compiler-pluginの設定もあります。長いですが、変に抜粋すると分かりにくいと思うので全部のっけておきます。
Springが使えるようになったらMainクラスは以下のような感じで書きます。
package jp.ohnaka.springmybatis;
import jp.ohnaka.springmybatis.model.Card;
import jp.ohnaka.springmybatis.service.CardService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
// Springからオブジェクトを取得す為に必要な初期化
ApplicationContext context = new ClassPathXmlApplicationContext("spring-setting.xml");
// 上で初期化したアプリケーションコンテキスト(アプリケーションの実行環境)から
// CardServiceのインスタンスを取得
CardService service = (CardService)context.getBean("cardService");
(以下続く)
DIコンテナであるSpringを使ってインスタンスを得る為には、まずApplicationContextというオブジェクトを取得する必要があります。Appliation Contextとは直訳すれば「アプリケーションの文脈」ですが、意味としては「アプリケーションの動作環境」というような感じです。DIを使うと設定次第でアプリケーションの挙動を細かく制御できるようになります。ApplicationContextはその設定結果というわけです。
つまり、
ApplicationContext context = new ClassPathXmlApplicationContext("spring-setting.xml");
という文は「クラスパスに置いたXMLファイルからアプリケーションコンテクスト(動作環境)を取得してね」という意味です。上記例だと、クラスパスのルートにあるspring-setting.xmlというファイルを読む事になります。
具体的には以下のようなファイルになります。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- サービスクラスの定義 -->
<bean id="cardService" class="jp.ohnaka.springmybatis.service.CardService">
</bean>
</beans>
Springはこの設定を読むと、「”cardService”というIDをもったBeanは jp.ohnaka.springmybatis.service.CardServiceImpl クラスですよ」という事を理解します。この設定によって、
CardService service = new CardService();
の代わりに、
CardService service = (CardService)context.getBean("cardService");
としてインスタンスを得る事ができるようになります。
なお、getBean()は指定したIDのBeanを取得するメソッドです。この例だと”cardService”という値が設定ファイルに書いたBeanのidの値と同じになっています。
何でもかんでもインターフェース
DIを使う時の「お作法」として、「DIコンテナにインスタンスを作らせたいクラスには、そのクラスと同じメソッドを持ったインターフェースを定義して、ソースコード中はインターフェースを使うようにした方がよい」というのがあります。CardServiceの例の場合、CardServiceはインターフェースにして、実際のロジックはCardServiceインターフェースを実装したCardServiceImplに書くようにします。
めんどくさいですね。。。
めんどくさいのですが、ちょっと我慢しましょう。そうする事によって、「DIコンテナが実装クラスに手を加える事ができるようになる」という大きなメリットがあるのです。(※ CGLIBなどの特殊なツールを使うと、インターフェースが切られていなくても実装に手を突っ込む事ができるので、必ずしも必須ではないのですが)
ということで、CardService.javaは単なるインターフェースとし、実装はCardServiceImpl.javaに移動します。それに伴い、先ほどのコンフィグファイル(spring-setting.xml)のcardServiceの実装クラス名を修正します。
<!-- サービスクラスの定義 -->
<bean id="cardService" class="jp.ohnaka.springmybatis.service.CardServiceImpl">
</bean>
CardServiceの定義
ではCardService.javaの中身を見てみましょう。
package jp.ohnaka.springmybatis.service;
import jp.ohnaka.springmybatis.model.Card;
public interface CardService {
Card getCard(String id);
Card createCard();
void updateCard(Card card);
void deleteCard(String id);
int getNumOfCards();
}
各メソッドの意味はわかりますね。それぞれDBに体してCRUD操作、つまりINSERT、SELECT、UPDATE、DELETEの操作を行います。getNumOfCardsメソッドはテーブルの行数を数えるSELECT文を発行するイメージです。
このCardServiceインターフェースを実装したCardServiceImplクラスの中で実際にDBを操作します。
CardServiceImplの詳細に入る前に、DB操作のためのフレームワークであるMyBatisの準備をしましょう。
MyBatisでDBを操作する準備
MyBatisを使うとDBのCRUD操作をスマートにかけるようになります。と言ってもHibernateに代表されるJPAを実装したO/Rマッパの様な高度なものを期待してはいけません。MyBatisでは基本的にSQLを自分で書きます。
ただ、一度でもHibernateを使った方はわかると思いますが、あれも全自動ではありません。むしろ、「思った様なSQLが発行されないー!!」とイライラする事もしばしば。バージョンによって挙動が変わるので、いつまでも古いバージョンで使い続けたり。
MyBatisは「全自動にしない」と諦める事で、よりシンプルに、確実に挙動が決まるコードを書く事ができます。
MyBatisにもイロイロな使い方がありますが、ここではMapperというものを使った方法で作って見ます。Mapperを使うためにはまず次の様なインターフェースを用意します。
package jp.ohnaka.springmybatis.persistence;
import jp.ohnaka.springmybatis.model.Card;
public interface CardMapper {
Card getCardById(String id);
void createCard(Card card);
void updateCard(Card card);
void deleteCard(String id);
int getNumOfCards();
}
このインターフェースでは、CardクラスとDBとの間のやりとり、つまりCRUD操作を書きます。メソッド名やパラメータなどに制約はありません。このインターフェースにはどんな名前のメソッドをいくつ書いても構いません。制約がないという事はMyBatis以外の他のコンポーネントと「粗結合である」という事です。Springをはじめとした様々なフレームワークと相性が良いのです。
しかも、Mapperインターフェースの実装クラスは、書く必要がありません。
実はMyBatisではSQLをXMLファイルに書くだけでMapperの実装ができてしまいます。CardMapperのXMLはこんな感じです。ファイル名はCardMapper.xmlとして、CardMapper.javaと同じ階層のクラスパスに置いておくと良いです。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jp.ohnaka.springmybatis.persistence.CardMapper">
<select id="getCardById" parameterType="string" resultType="Card">
SELECT * from CARD where ID = #{id}
</select>
<insert id="createCard" parameterType="Card">
INSERT INTO CARD
(ID, NAME)
VALUES
(#{id}, #{name})
</insert>
<update id="updateCard" parameterType="Card">
UPDATE CARD set
name = #{name}
where id = #{id}
</update>
<delete id="deleteCard">
DELETE from CARD
where ID = #{id}
</delete>
<select id="getNumOfCards" resultType="int">
SELECT count(*) from CARD
</select>
</mapper>
XMLの中身を見るだけで何をしているかわかってしまうくらい単純ですね。このXMLから実装が勝手にできてしまうのですが、いったいどうやってnewしたら良いんでしょう?
!!!!!
そうです、Springが作ってくれるのです!spring-setting.xmlに、次の様な定義を足してください。
<!-- CardServiceで使う CardクラスのMapperを定義 -->
<bean id="cardMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="jp.ohnaka.springmybatis.persistence.CardMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
このBeanは MyBatis-Springライブラリ が提供するMapperFactoryBeanというものを使っています。Springでは、Bean定義のclassにFactoryBeanインターフェースを実装したクラスを指定するとすこし挙動が変わります。Bean定義は通常classに指定したクラスがそのまま生成されますが、FactoryBeanを指定した場合はファクトリクラスとして扱われ、そのクラスの getObject()メソッドが返すオブジェクトがBeanとして使われます。つまり上記定義の意味は「cardMapperというidを持つBeanはMapperFactoryBeanから作ってください」という意味になります。
このMapperFactoryBeanには以下の二つのプロパティがあります。
一つ目は mapperInterface
です。これは今作ろうとしているMapperのインターフェースをFQCN(パッケージ名も入れた完全なクラス名)で指定します。
二つ目は sqlSessionFactory
です。sqlSessionFactoryとはMyBatisでDBとの接続(Session)を生成する役目を持ったものです。このプロパティにはvalueではなく ref という指定が書かれています。refとは「他のBeanを参照」という意味で、この場合は「”sqlSessionFactory”というid値を持ったBeanを sqlSessionFactoryとして使う」という意味になります。
sqlSessionFactoryのBean定義は次のようにします。
<!-- MyBatisのSqlSeessionFactoryの定義 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- MyBatisの設定ファイルの場所. TypeAliasを使いたいので読み込ませている. -->
<property name="configLocation" value="classpath:/jp/ohnaka/springmybatis/persistence/mybatis-config.xml"/>
</bean>
このsqlSessionFactoryというBeanはDBへの接続をデータソースから取得するようにしています。データソースというのは JavaEEの DataSourceの事です。TomcatやJBossなどのアプリケーションサーバ上でアプリを動かす場合はJNDIでルックアップできますが、今回は単体のJavaアプリなので、JNDIサーバなどがいません。
というわけでデータソースもSpringに作ってもらっちゃいましょう。以下のようにしてdataSourceというIDでデータソースのBeanを作成します。
<!-- データソースの定義. アプリケーションサーバ上で動かす場合はJNDIから取ってくるようにしても良い -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://hostname/card?useUnicode=true&characterEncoding=UTF-8" />
<property name="username" value="hogehoge" />
<property name="password" value="passpass" />
</bean>
このデータソースはSpringが提供している DriverManagerDataSourceと言うものを使って作っています。DriverManagerという名前からもわかるように、JDBCドライバのクラスから直接データソースを作ってくれるものです。他にもDBCPのorg.apache.commons.dbcp.BasicDataSourceクラスを使って作ったりもできますので、環境に合うように変更してください。
これでようやくCardMapperのBeanが完成しました。
Springが組み立てたCardMapperのインスタンス(Bean)を使う
これまでの設定で、Springは “cardMapper”というidでCardMapperインターフェースを実装したBeanを作れるようになりました。このcardMapperインスタンスをプログラム(CardServiceImpl.java)から使いたいと思います。
SpringからBeanを取得する方法は、先ほどMainクラスでCardServiceインスタンスを取得したのと同じ様に、ApplicationContextから作っても構いません。次のような感じになるでしょうか。
CardMapper mapper = (CardMapper)context.getBean("cardMapper");
ただ、この方法だと、インスタンスが欲しい場所でいちいちApplicationContextを用意してあげないといけないので不便です。ここで必要なのが「逆転の発想」、つまり 「IoC : Inversion of Control」です。
IoCについてはDIという言葉の名付け親でもあるMarti Fowler氏の論文を読んでみると良いかもしれませんが、要するに「欲しいところでnewする」のではなく「使いたい場所ではすでにそこにある」ようにしてあげるのです。
具体的に見て行きましょう。さっきインターフェースまで作ったCardServiceですが、この「逆転の発想」を使って実装クラス(CardServiceImpl)を作るとこんな感じになります。
package jp.ohnaka.springmybatis.service;
import java.util.UUID;
import jp.ohnaka.springmybatis.model.Card;
import jp.ohnaka.springmybatis.persistence.CardMapper;
public class CardServiceImpl implements CardService {
private CardMapper cardMapper_;
public void setCardMapper(CardMapper cardMapper) {
cardMapper_ = cardMapper;
}
@Override
public Card getCard(String id) {
return cardMapper_.getCardById(id);
}
@Override
public Card createCard() {
Card test = new Card();
test.setId(UUID.randomUUID().toString());
test.setName("No Name");
cardMapper_.createCard(test);
return test;
}
@Override
public void updateCard(Card card) {
cardMapper_.updateCard(card);
}
@Override
public void deleteCard(String id) {
cardMapper_.deleteCard(id);
}
@Override
public int getNumOfCards() {
return cardMapper_.getNumOfCards();
}
}
このCardServiceImplクラスはメンバとして cardMapper_ というメンバを持っていますが、どこにも初期化コードはありません。new しているわけでも無いですし、SpringのApplicationContextからgetBean()メソッドで取得している訳でもありません。
SpringではこのcardMapper_メンバのようにpublicなsetter/getterを持っているものは「CardServiceImplのプロパティ」と考えられ、XMLファイルで値をセットする事ができます。もう一度最初の spring-setting.xmlを思い出すと次のように書いてありました。
<!-- サービスクラスの定義 -->
<bean id="cardService" class="jp.ohnaka.springmybatis.service.CardServiceImpl">
</bean>
これを、次のように修正します。
<!-- サービスクラスの定義 -->
<bean id="cardService" class="jp.ohnaka.springmybatis.service.CardServiceImpl">
<property name="cardMapper" ref="cardMapper" />
</bean>
もうおわかりですね。このようにタグを書いてあげると、Springから cardServiceのインスタンスを取得した際に、あらかじめ中身をセットされた状態で渡してくれるようになるのです。cardMapperプロパティの中身は ref=”cardMapper” となっていますので、先ほど説明した通り、”cardMapper”という idを持ったBeanが生成され、セットされるというわけです。
ようやく全てが繋がった
これでようやく全てが繋がりました。Mainクラスから順に、次のような順で繋がっています。
Main
|
CardService
CardServiceImpl
|
CardMapper
CardMapperImpl (CardMapper.xmlを元にMapperFactoryBeanによって自動的に作られたインスタンス。僕らは意識する必要はない)
|
SqlSessionFactory
|
DataSource
このうち、オブジェクトを取得するコードをプログラムで明示的に書いたのは Main – CardService(Impl)の所だけで、残りはすべて Springによって自動的に組み立てられています。このチェーンを少し書き直してみると次のようになります。
Main
↓ ApplicationContext#getBean()で取得
CardService
CardServiceImpl
↑ Springにより注入
CardMapper
CardMapperImpl (CardMapper.xmlを元にMapperFactoryBeanによって自動的に作られたインスタンス。僕らは意識する必要はない)
↑ Springにより注入
SqlSessionFactory
↑ Springにより注入
DataSource
このように、最初のとっかかりだけgetBean() を明示的に読んでいますが、あとは勝手に注入されて組み立てられたオブジェクトが芋づる式に取れているのがお分かりかと思います。
DIによって得られたもの
さて、これでいったい何が得られたのでしょうか?ちょっぴりややこしくなって newが無くなっただけでしょうか?DIによって得られるメリットはその道の人に語ってもらえば何時間でも話してくれそうですが、僕なりに感じているメリットをいくつかかいつまんで紹介します。
CardServiceImplクラスがMyBatisやSpringに依存していない
この長いエントリでさんざんMyBatisやSpringの説明をしてきたのに意外かもしれませんが、CardServiceImplクラスのどこをみてもMyBatisやSpringのクラスを利用しているところはありません。これは結構すごい事です。将来MyBatisではない別のO/Rマッパを使った場合でもCardServiceImplを一行も書き換えずに済むと言う事です。
「将来別のO/Rマッパと差し替えたりしないよ」
という人もいるでしょう。僕もそうです(笑)。ではこの依存性の排除は無意味なのでしょうか?僕も実は長い間そういう風に感じていたのですが、ちょっと変わってきました。最近の僕の考えは、
「依存性を排除するのは差し替えの為ではなくて、プログラミングスタイルを統一する為に行なう」
という考えです。例えばJDBCを生で使っていた頃を思い出すと、アプリケーションを作るたびに 「SQLExceptionをどこでキャッチするか?」という問題と格闘していたように思います。今回のCard管理アプリケーションを見直してみてください。どこにもSQLExceptionを処理しているところはありません。それどころか、トランザクションのcommit()や rollback()すら読んでいるところがありません。
びっくりしませんか?
つまり、永続化層にDBを使うかファイルシステムを使うかによらず、CardServiceのプログラミングスタイルを統一して書けるという事になります。このスタイルに慣れてしまえば、サービス層を書く時に「今回はどうやって書くかなぁ」とか悩まなくて良くなるのです。
今回のサンプルはDBを呼ぶだけの単純なアプリでしたが、実際にはフロントエンドにRESTfulなAPIを開けたりなんだりと、いろんなレイヤのプログラミングが混ざってきます。全てがDIによって構築されるようになると、各レイヤのプログラミングスタイルが他のレイヤに影響される事がなくなるので、さくさくとコーディングを進められるようになるのです。
CardMapperがMyBatisやSpringに依存していない
MyBatisの設計思想がDIの考え方に沿ったものになっているおかげでもありますが、CardMapperインターフェースにはどこにもMyBatisに依存したクラスが出てきていません。
将来MyBatisを使わずにJDBCドライバを直接叩きたくなったり、DBの代わりにファイルシステムを使いたくなったら、CardMapperインターフェースを実装したクラスを作りさえすれば良いのです。
「プログラミングスタイルの統一」という観点では、「オブジェクトを永続化するレイヤは◯◯Mapperというインターフェースを作る」というスタイルに統一できるメリットがあると言えます。
トランザクション管理を外部化できる
今回は説明しきれませんでしたが、Springを使うと、DBのトランザクション管理、つまり commitや rollbackをプログラム中で明示的に書く必要がなくなります。お恥ずかしながら、SpringがDBのトランザクション管理までを見据えて作られているということに最近まで気がついていませんでした。
僕の中では「トランザクション管理はアプリケーションサーバ上で動かす時にJTAとかを使ってやるもの」という意識がありましたが、「アプリケーションサーバではなくDIコンテナに任せるとこんなにすっきりするんだ」というのに少々驚いています。
このSpringによるトランザクション管理についての説明はまた別の機会に行ないたいと思います。
アスペクト指向プログラミング(AOP)ができる
今回の例を見てみると分かるように、注入されるクラスには必ずインターフェースがあり、プログラムのコード中に実装クラスをインスタンス化するコードが一切でてきません。と言う事は、実装クラスにプロキシクラスを挟むのが容易にできると言う事を意味します。
例えばCardServiceの各メソッドを呼び出す際にかならずログを出力したいとしましょう。CardServiceの実装はSpringから取得しているので、SpringにはCardServiceImplの代わりにCardServiceインターフェースを持ったProxyクラスを作って返してもらう事ができます。そのProxyクラスの中では呼び出されたメソッドの名前をログに出力したあと、CardServiceImplに処理を委譲するようにすれば、MainクラスにもCardServiceImplにも手を加えずにメソッドの前に処理を注入できる事になります。
こういったプログラミングを「アクペクト指向プログラミング」などと言ったりします。DIコンテナがいない場合でも、JavaassistやCGLIBのようなトリッキーなツールを使えばAOPができるのですが、トリッキーなものはなるべく使わないで済むほうが嬉しいですよね。
おまけ
最後に、細切れになってしまった spring-setting.xmlの全体を示しておきます。あと、mybatis-config.xmlの内容も。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- データソースの定義. アプリケーションサーバ上で動かす場合はJNDIから取ってくるようにしても良い -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://hostname/card" />
<property name="username" value="hogehoge" />
<property name="password" value="passpass" />
</bean>
<!-- MyBatisのSqlSeessionFactoryの定義 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- MyBatisの設定ファイルの場所. TypeAliasを使いたいので読み込ませている. -->
<property name="configLocation" value="classpath:/jp/ohnaka/springmybatis/persistence/mybatis-config.xml"/>
</bean>
<!-- CardServiceで使う CardクラスのMapperを定義 -->
<bean id="cardMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="jp.ohnaka.springmybatis.persistence.CardMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<!-- サービスクラスの定義 -->
<bean id="cardService" class="jp.ohnaka.springmybatis.service.CardServiceImpl">
<property name="cardMapper" ref="cardMapper" />
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- changes from the defaults -->
<setting name="lazyLoadingEnabled" value="false" />
</settings>
<typeAliases>
<typeAlias alias="Card" type="jp.ohnaka.springmybatis.model.Card"/>
</typeAliases>
</configuration>