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とspringspringの再入門 - DI(依存性注入)