LoginSignup
1
3

More than 3 years have passed since last update.

【#1.SpringBoot】SpringBootとは? 〜DIについて〜[WIP]

Last updated at Posted at 2020-07-26

はじめに

この記事では、「Spring Boot」を利用したアプリケーション開発・構築において、
最初に引っかかるであろう「DI」、「AOP」についてまとめています。
今回は1章目として、DIについてまとめていきます。

今回の記事は、奥深くまで追求するというより、SpringFramework,SpringBootを触り始める
導入としての記事になります。

※Javaの一般的な知識や、SpringFrameworkについては理解がある前提で記事をまとめますが、
できる限り初学者に分からなさそうな部分は、掘り下げて説明をしていきます。

目次

 1.SpringBootって?
 2.SpringFrameworkとは?
 3.DIとは?
 4.DIとDIコンテナ

1.SpringBootって?

SpringBootとは、Springアプリケーション(SpringFramework)をより簡単な、設定や準備、簡潔なコード(アノテーション)で
効率的に開発+デプロイができるようになる、フレームワークと考えてください。
Springフレームワーク のための フレームワークといった立ち位置のイメージです。

上記のように、SpringFrameworkをより良く利用しやすくなるといった点から、
 ・SpringFrameworkはどんなものか?
 ・SpringFramework単体に比べ、どのような良さがあるのか?(ここは総じて、次回の記事でまとめる)
といった点を理解するのが、SpringBootの理解に繋がるかと思うため、
SpringFrameworkに関してまとめる。

2.SpringFrameworkとは?

Javaのフレームワークの一つで、DIやAOPといった仕組みが導入されている、部分が大きな特徴です。
また、SpringFrameworkを軸として、SpringCloud,SpringBatch,SpringShell,SpringSecurityのような
多種多様に渡る汎用的に利用可能な、プロジェクトも提供されているのも大きな特徴です。
SpringFrameworkの教育コストをかけておくことで、将来的にいろんなシステムパターン(バッチ等も含めて)
構築できていく部分は、利点かと思います。

いや、そもそも...DIや、AOP....聞いたことあるけど、良くわかってない...というのが大半だと思うので、
その辺りも含めて、話をしようと思います。
今回は”DI”についてまとめていきます。

3.DIとは?

DependencyInjectionの略で、DIと言います。
日本語の意味としては、「依存性の注入」と表現します。
いや、意味わからんねん。ってのが感想だと思います。笑

私も、この記事をまとめるにあたって、自己の認識が誤ってる部分も含めて
再学習を行いましたが、以下の記事がとてもわかりやすく、
こちらの表現だと、「オブジェクトの注入」という考え方がしっくりきます。

https://qiita.com/ritukiii/items/de30b2d944109521298f

では、このDIとは実際にどんなものかを見ていきましょう。
以下2つが分かれば、ある程度DIについては理解できるかと思います。

1.従来どんな形式だったコードを、DI使うとどんなコードになるのか?
2.DIを利用することによって、どんな利点があるのか?
を確認していきましょう。

まずは、DIを利用しないパターンのサンプルコードをSpringBootアプリで作ってみます。
以下のリポジトリに置いているので、java11,mavenインストールしてる方は、ご自由にどうぞ。
https://github.com/sasdos-engee/Practice-DI

今回作成するsampleコードは、以下のようなrestAPIをイメージしています。

◽️仕様
機能:「POSTで受けたリクエストを元に、学生情報を登録するAPI」

MVC系のクラス
・Controller ・・・ リクエストを受けて、サービス層を呼び出し、結果に応じてレスポンスを返す。
・Service ・・・ 業務的なチェックを実施後、登録処理を行うためrepository層呼び出し。
・Repository ・・・ DB登録処理を実施。

モデル系のクラス
・Student ・・・ 今回登録する単位となる、学生クラス
・request ・・・ POSTでリクエストを受ける際のオブジェクト
・response ・・・ レスポンスのオブジェクト


DIを利用しないパターン

対象のクラスは以下です。
 ・SampleNonDiController
 ・SampleNonDiSerivce
 ・SampleNonDiRepository
 ・RegistStudentRequest
 ・RegistStudentResponse
 ・Student

今回は、DIを使わない・・・>インスタンスは全て各クラスの中でインスタンス化しています。

さあ、ぱっと見る感じ、これでも動きそう。
はい、動きます。 ではどんなケースに困るのか?

ControllerとService層、Repository層がそれぞれ違うメンバーで
実装していたと仮定しましょう。
それぞれのメンバー(A~Cさん)は、自分の担当層の実装、およびUTの実装を行います。

AさんのUTに、フォーカスして考えてみましょう。

AさんがUTにて担保するのは、
 ・リクエストに対して、想定通りのオブジェクトを引数に、service層を呼び出せるか?
 ・service層の戻りパターン(今回だと1 or 0)に応じた正しいレスポンスを返せるか?
である。 それは即ち、service層の振る舞いに左右されることなく、想定の結果が得られるか?
=> service層をモック化してテストを実施することになると思います。

今回Service層の実装が完了していないとして、モック化を考えてみましょう。
・・・と調べてみると分かると思います。
基本的には、JUnit等の実装にて、「メソッド内でインスタンス化している別クラスはモック化”できません”」。

例外的に、JMockitoライブラリや、PowerMockライブラリでモック化はできないことはないですが、
それぞれのライブラリ内の機能が非推奨や、利用できなくなることが多いうえに、
利用しているJUnit,Spring等のフレームワークバージョンとの相性等も考える必要があるため、
できれば利用したくないです。
よっぽどのことがなければ、上記のようなライブラリを使わずとも済むような設計でいきたい。

「そうなると....メソッド内でのインスタンス化を辞めるしかない。」にたどり着きます。
打開策として、newしているものを、引数として実態を渡してもらうこととしましょう。
service層にフォーカスして実装を組み替えてみましょう。


引数としてインスタンスを渡してもらう方式にしてみる

対象のクラスは以下です。
 ・SampleNonDiVer2Controller
 ・SampleNonDiVer2Serivce
 ・SampleNonDiVer2Repository

Serviceクラスをみると、メソッド内でのインスタンス化がなくなりましたね。
これで、Service層のモック化が可能になりました。(引数のrepositoryInstanceの実態を、適宜
テスト側で変更することで、可能)

おっ、これで問題解決では?と感じますが、これではダメです。
なぜなら、Service層だけを見ると解決ですが、Controller層側の問題が深刻になります。
Service層を利用する側のController層が
Repository層のインスタンスも用意する必要が出てきて、いよいよカオスになります。

こうなってくると、serviceクラスのコンストラクタで、repository層のインスタンスを
設定するようにしたとしても、結局呼び元(Controller)でのインスタンス生成が避けれません。
これでは、クラスの依存階層が深ければ深くなるほど、呼び元でのインスタンス定義が増える一方で
根本解決になっていません。

そうなってくると、 Controller層で意識するServiceクラスのインスタンスを
適宜入れ替える仕組み(注入)がないと、UTも含めて根本解決できない。
同じように、Service層で意識するRepositoryクラスのインスタンスも同じです。

ここで出てくるのが、DI(依存性の注入=オブジェクトの注入)です。


DI利用の場合

対象のクラスは以下です。
 ・SampleDiController
 ・SampleDiSerivce
 ・SampleDiRepository

おいおい、アノテーション(@)付けるだけでよしなにやれてるの?って感じです。
そうです、これがDIになります。

コード的にはこれで、うまくやれてんだと分かりますが、中身が意味わかんないですね。
なので、DIについてももう少し掘り下げましょう。
DIを実現するにあたって必要な”DIコンテナ”という仕組みを覚えましょう。

4.DIとDIコンテナについて

DIは上記で作成したコードのように、
 ・クラス間での依存性の排除(厳密には、各クラスのメソッド内でのinstance化の排除)
 ・依存していたクラスの注入(オブジェクトの注入)
することで実現できました。(できたようです)

みた感じ、各クラスに @Serviceやら@Repositoryをつけたり、
依存していたクラスを @Autowiredをつけた、privateフィールドで定義しているくらいですね。
これらが、実際に何をしているのか含めて見ていきましょう。

DIコンテナについて

上記DI利用の場合のソースを見てみましょう。

SampleDiControllerのソース内のprivateフィールド
"sampleDiSerivice"に着目してみましょう。
今回、sampleDiServiceをメソッド内から外だしすることで、DI化に成功しました。
※上記ソースでは、SpringFrameworkを使ったDIコンテナを利用したDIになってます。
 (説明上分かり辛いのは、お許しを。)

しかし、プライベートフィールド化できたところで、
結局インスタンスの実態をクラスを利用する側が、生成する必要があります。
今回の例だと、SampleDiControllerを利用するモジュール側で、
SampleDiControllerのコンストラクタを利用する等して、sampleDiServiceのインスタンスを渡す等ですね。

それに、今回はSampleDiServiceの一つだけですが、利用する依存クラスが多いほど
DI時に呼び元が渡すインスタンスの数は膨大に増えていきます。
(コンストラクタにどんどん引数増えます)

それは嫌だな...なんか簡単にやる方法ないのかな.....と編み出されたのが、
”DIコンテナ”の仕組みです。

DIコンテナは一言で表すと”インスタンス生成+よしなにインスタンスを渡す(DI)”機能を持ったものです。

SpringFramework(Boot)での DI と DIコンテナについて

上記で説明したように、
DIコンテナに インスタンス生成や、よしなにインスタンスを渡す(DIする)ために、
DI対象のクラスを宣言する必要があります。

それらを実施しているのが、
@Component および @Controller,@Service,@Repositoryのアノテーションです。

私自身も学習する中で、以下の記事が一番理解しやすかったです。

https://blog.mookjp.io/blog-ja/spring-framework%E3%81%A8di%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/

それぞれのアノテーションについて説明をしていく。
@Controller@Service,@Repositoryは、@Componentを継承した形で アーキテクチャに則ったアノテーションになっているので、
@Componentについて説明する。
このアノテーションをつけることで、”コンポーネントスキャン”という機能により、DIコンテナにBean登録されます。
これにより、DIコンテナ内にシングルトン(※最後の項で説明します)なクラスとして、インスタンスが登録されているという訳です。

では、DIコンテナからインスタンスを取り出して、注入してくれるのは誰か?
そうです。@Autowired アノテーションです。
このアノテーションを利用することで、DIコンテナに登録されているインスタンスを、よしなに注入してくれます。

これで、SpringでのDI,DIコンテナの仕組みは、大体理解できたかと思います。

「おおお!じゃあ、基本的に独自クラスは、全部 @Componentつけて、 @Autowired つけまくれば
 メソッド内でnewせずとも、動作できるんですね!!!」
と思うところですが、1点危険な点があるので注意しておきましょう。

DIの注意点(シングルトンについて)

上記のように、@AutowiredをつけてDIできることが分かったと思いますが、
これらの”DI”はDI対象のクラスが 「シングルトン」というものになってます。

このシングルトンとは、インスタンスが1つしかないことを意味しています。
つまり、A,B,Cのクラスに対して、 DというクラスをDIした場合、
A,B,Cで利用されているDのインスタンスは同様のものということになります。
当然、Dの中で保持している、フィールドも共有ということになります。

今回、例示しているソースで説明すると、
StudentというクラスをDI対象にすると、
Studentクラス内の”number","name","furigana"のそれぞれの値を共有することになります。

ん?何か起きるの?悪いことがあるの? => はい。設計次第では致命的です。

どんなことが起きてしまうのか?については、以下がわかりやすくまとめてありました。

https://qiita.com/NagaokaKenichi/items/34bc62e572256c5710e1

何も考えずにDI対象のクラスが、状態や変数を持っておくと
スレッドセーフな作りにならず、WebAPIとして致命的な作りになってしまう可能性があります。
(Aさんの個人情報が、Bさんに見えてしまったりするよ)

なので、DIを利用する場合は、以下のどちらかの方針で気をつけましょう。

 案1.各スレッドで共有すると困るような、フィールドはクラスに持たせない作りにする。
  (いわゆる、スレッドアンセーフになってしまうような変数をそもそもDI対象には持たせない。)

 案2.@Scopeを用いて、インスタンスのスコープを変更する。
   SpringのDIでは、デフォルトがシングルトンになっているため、 @ScopeアノテーションをDI対象のクラスにつけて、
   スコープの指定が行えます。
   対象クラスの呼び出しの度、 セッション毎、 リクエスト毎等、複数のスコープ設定が存在するため、
   各スレッドで競合が起きてしまわないような、スコープ定義を行ってスレッドセーフな作りにする。
   
案2は、シングルトンにしない時点で、多数のインスタンスを作り上げることを考えると、
できる限り、設計でスレッドセーフを回避して、案1によせるほうがいいのか?という印象を受けました。

ここまでで、DIについては粗方理解できたかと思います。
次回は、AOPと、SpringBootの特徴についてまとめていこうと思います。

1
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3