Springフレームワークのススメ2

はじめに

前回の投稿では、簡単な例を交えながらSpringフレームワークの雰囲気を掴んだ。
今回の投稿からは、もう少し踏み込んだ内容についてまとめていく。

キーワード:DI、DIコンテナ

DIとは?

DIはDependency Injectionの頭文字で、直訳すると「依存の注入」である。
依存の注入とはどういうことか。これは、言い換えるとオブジェクトの注入ともいえる。
以下のサンプルプログラムでは、そのDIを行っている。

■ICalculatorインタフェース

package jp.co.myApp.calc_if;

public interface ICalculator {
	public int calc(int num1, int num2);
}

■PlusCalcクラス

ICalculatorインタフェースの実装クラス。足し算をしてその結果を返す。

package jp.co.myApp.calc_executor;

import jp.co.myApp.calc_if.ICalculator;

public class PlusCalc implements ICalculator {
	public int calc(int num1, int num2) {
		return num1 + num2;
	}
}

■MinusCalcクラス

ICalculatorインタフェースの実装クラス。引き算をしてその結果を返す。

package jp.co.myApp.calc_executor;

import jp.co.myApp.calc_if.ICalculator;

public class MinusCalc implements ICalculator {
	public int calc(int num1, int num2) {
		return num1 - num2;
	}
}

■CalculateExecutorクラス

package jp.co.myApp.calc;

import jp.co.myApp.calc_if.ICalculator;

public class CalculateExecutor {
	private ICalculator calculator;

	public void setCalculator(ICalculator calculator) {
		this.calculator = calculator;
	}

	public int doCalculate(int num1, int num2) {
		return this.calculator.calc(num1, num2);
	}
}

■Mainクラス

package jp.co.myApp.calc;

import jp.co.myApp.calc_executor.PlusCalc;
import jp.co.myApp.calc_if.ICalculator;

public class Main {

	public static void main(String[] args) {
		/*
		 * setup
		 */
		ICalculator calc = new PlusCalc();
		CalculateExecutor executor = new CalculateExecutor();
		executor.setCalculator(calc);

		/*
		 * execute calculation
		 */
		int result = executor.doCalculate(1, 2);
		System.out.println("result: " + result);
	}
}

■結局どこでDIしてるの?

CalculateExecutorクラスのsetCalculatorメソッドでDIされている。
Mainクラスのmainメソッド(setupの部分)でDIに必要なオブジェクトを生成し、CalculateExecutorに対してそのオブジェクトを注入している(=依存を注入している)。

■問題点

上記プログラムでは足し算の結果を得るためにPlusCalcオブジェクトを使っている。
しかし、もし引き算の結果を得ようと思ったときは、
 1.jp.co.myApp.calc_executor.MinusCalcをimportする。
 2.PlusCalcでなくMinusCalcをimportする。
という具合に、ソースコードに直接修正を加えないといけなくなる。

DIコンテナとは?

一言でいえば、DIを行う仕組みを提供してくれるフレームワークだろうか。

上記プログラムではMainクラス内で、計算に必要なオブジェクトを生成してそれをDIに使っている。
今回の例ではオブジェクトの生成だけなので行数は少ないが、その他オブジェクトの設定(定義)を行う場合は、処理の行数がさらに多くなる。
また、上記問題点で述べたように、ソースコードを直接修正する必要も出てくる。

DIするために必要なオブジェクトの設定(定義)と、それを使った処理(実装)を切り分けることで、
 1.ソースコード内で何行もかけて書いていたオブジェクトの定義が不要になる。
 2.要求の変更が起こった時の修正量を最小限に抑えることができる。
という利点が生まれる。

この仕組みを提供してくれるのがDIコンテナであり、SpringフレームワークはこのDIコンテナのひとつである。

DIコンテナ(Springフレームワーク)によるDI

先述のソースコードと差分があるものを本セクションに記載する。
まずは設定ファイルから。

■applicationContext.xml

この設定ファイルにはオブジェクトの定義情報が書かれている。
ミソは7行目~11行目。

7行目~9行目 ExecuteCalculatorのオブジェクト定義。plusCalcというIDのオブジェクトがDIされていることが記載されている。
10行目    plusCalcというIDでオブジェクトが管理されている、そのオブジェクトはPlusCalcである、と記載されている。
11行目    minusCalcというIDでオブジェクトが管理されている、そのオブジェクトはMinusCalcである、と記載されている。
<?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.xsd">

	<bean id="executor" class="jp.co.myApp.calc.CalculateExecutor">
		<property name="calculator" ref="plusCalc"></property>
	</bean>
	<bean id="plusCalc" class="jp.co.myApp.calc_executor.PlusCalc" />
	<bean id="minusCalc" class="jp.co.myApp.calc_executor.MinusCalc" />

</beans>

■Mainクラス

上記applicationContextの設定に則って、実行するとコンソール上に1と2の和である3が表示される。
DIコンテナを使えば、もはやプログラム内で実装クラス(今回の例だとPlusCalc)をnewしてExecuteCalculatorにセットする必要が無くなる(DIコンテナを使っていないMainクラスの例と見比べてみてほしい)。
オブジェクトの生成およびDIはapplicationContextでやってくれるため、開発者は生成されたオブジェクトを使って必要な処理のみをプログラムに記載すればよい。

ちなみに1と2の差を出したい場合はapplicationContext.xmlの「ref="plusCalc"」を「ref="minusCalc"」に変えるだけでいい。
ソースコードをいじる必要は無く、修正量が最低限で済む。

今回の例では足し算と引き算クラスのみを作成しているが、今後もし掛け算クラスや割り算クラスが増えた時も、applicationContextにbean定義を記載しておくことで、変更に強いプログラムを実装することができる。

package jp.co.myApp.calc;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

	public static void main(String[] args) {
		try {
			/*
			 * setup
			 */
			ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
			CalculateExecutor executor = context.getBean(CalculateExecutor.class);

			/*
			 * execute calculation
			 */
			int result = executor.doCalculate(1, 2);
			System.out.println("result: " + result);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

■ExecuteCalculatorクラス

DIコンテナを使っていないExecuteCalculatorとの違いは、@Componentアノテーションと@Autowiredアノテーションが付いている点にある。このアノテーションについては次回以降でまとめていく予定のため、今回は割愛する。

package jp.co.myApp.calc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import jp.co.myApp.calc_if.ICalculator;

@Component
public class CalculateExecutor {
	private ICalculator calculator;

	@Autowired
	public void setCalculator(ICalculator calculator) {
		this.calculator = calculator;
	}

	public int doCalculate(int num1, int num2) {
		return this.calculator.calc(num1, num2);
	}
}

終わりに

本投稿では、まず「DI」について簡単なプログラム例を交えて記載した。
直訳すると「依存の注入」だが、「オブジェクトの注入」とも言える。
そして次に「DIコンテナ」について述べるとともに、先述の例と比較しつつ、DIコンテナを使うことによるメリットも述べた。
Springフレームワーク等のDIコンテナを使うことで、開発者はオブジェクトの定義と実装を切り離すことができるようになる。

次回以降は、これまでちょくちょく出てきた「@Component」「@Autowired」「bean」等についてまとめていけたらと思う。

参考

今さら聞けないDiとspring
springの再入門 - DI(依存性注入)