Spring FrameworkのDependency Injection(以下DI)の機能について書きます。
"bought a bracelet"
DIとは?
いい例が浮かばなかったのですが、例えば以下のようなプログラムを考えてみてください。
// src/cc/co/goodpreparations/Investor.java
package cc.co.goodpreparations;
public class Investor {
public void buy() {
GoodCalculator calculator = new GoodCalculator();
// do something
}
}
// src/cc/co/goodpreparations/GoodCalculator.java
package cc.co.goodpreparations;
public class GoodCalculator {
public double calculate() {
// do difficult calculations
return 0;
}
}
何かに投資するInvestorクラスがあって、投資するために複雑な計算をします。その計算にGoodCalculatorというクラスを使おうというシチュエーションです。
上のように書いてしまうと、InvestorクラスはGoodCalculatorクラスが無いと使用できません。つまり、InvestorクラスはGoodCalculatorクラスに依存しています。一般にクラスが別のクラスに依存していると、
- クラスの独立性が低いため、再利用しにくい
- 独立したテストが行い辛い
というデメリットがあります。
Investorの例に戻ります。もし、今使われているGoodCalculatorより優秀なBetterCalculatorが開発されて、BetterCalculatorを使おう。となったらどうでしょう?GoodCalculatorを使っている場所をすべて書き直さなければなりません。使われている場所が1ヶ所だけならいいですが、何10箇所、何100箇所もあったら大変です。
以下のようにするとどうでしょうか?
// src/cc/co/goodpreparations/Calculator.java
package cc.co.goodpreparations;
public interface Calculator {
public double calculate();
}
// src/cc/co/goodpreparations/GoodCalculator.java
package cc.co.goodpreparations;
public class GoodCalculator implements Calculator {
@Override
public double calculate() {
// do difficult calculations
return 0;
}
}
// src/cc/co/goodpreparations/Investor.java
package cc.co.goodpreparations;
public class Investor {
Calculator calculator = null;
public Investor(Calculator calculator) {
this.calculator = calculator;
}
public void buy() {
// do something with the given calculator
}
}
こうすると、Investorは中身がどのように実装されているかは知りませんが、Calculatorインターフェースを実装した何らかのクラスを使うことになります。GoodCalculatorがBetterCalculatorに変更されても、そもそもGoodCalculatorというクラス名を直接ソースコード上に書いていないので変更は生じません。もしBetterCalculatorがcalculateメソッドを実装していなければ、Adapterデザインパターンを使ってCalculatorインターフェースを実装するクラスを作ればいいでしょう。
さて、実際にどのCalculatorを使用するかですが、設定ファイルに記述してSpring Frameworkに具体的なクラスを注入してもらいましょう。このように、ソースコードからクラス間の依存性を取り除き、外部から依存性を注入することをDIと呼びます。以下では、
- コンストラクタによるDI
- setterによるDI
の2種類のDIを説明します。
コンストラクタによるDI
全章の後半で導入したように、コンストラクタで使用するCalculatorを渡す場合は以下のようにすればDIができます。ポイントはXML設定ファイルのinvestorのbean定義のconstructor-arg要素の部分です。この部分でコンストラクタに渡す具体的なクラスを指定しています。注入する具体的なクラスを変えたい場合は、この部分を変更するだけでOKです。
// src/cc/co/goodpreparations/Main.java
package cc.co.goodpreparations;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Investor investor = (Investor)context.getBean("investor");
investor.buy();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- src/Beans.xml -->
<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="goodCalculator" class="cc.co.goodpreparations.GoodCalculator"></bean>
<bean id="investor" class="cc.co.goodpreparations.Investor">
<constructor-arg ref="goodCalculator"/>
</bean>
</beans>
setterによるDI
同様にsetterを利用して、具体的なクラスを注入することができます。
// src/cc/co/goodpreparations/Investor.java
package cc.co.goodpreparations;
public class Investor {
Calculator calculator = null;
public void setCalculator(Calculator calculator) {
this.calculator = calculator;
}
public void buy() {
// do something with the given calculator
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- src/Beans.xml -->
<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="goodCalculator" class="cc.co.goodpreparations.GoodCalculator"></bean>
<bean id="investor" class="cc.co.goodpreparations.Investor">
<property name="calculator" ref="goodCalculator"/>
</bean>
</beans>
setterを利用したDIでは、まず引数なしコンストラクタでInvestorクラスをインスタンス化した後、setterでcalculatorを設定しています。使用するcalculatorを変えたい場合は、investorのbean定義のproperty要素のref属性を変更すればOKです。
どちらを使うべきか?
コンストラクタを使用したDIとsetterを使用したDIを説明しました。「で、どっち使えばいいの?」という疑問が浮かぶと思いますが、必須の依存性を注入したい場合はコンストラクタによるDI、オプショナルな依存性を注入する場合はsetterを使用したDI、という使い分けをするのが一般的です。必要に応じて両方同時に使うことも可能です。