#はじめに
Spring Bootを使ったアプリケーションの実装を行ってきて1ヶ月が経った。
しかしまだ良くわかってない部分が沢山あるので勉強したところをここにノート変わりとしてまとめていく
#DI(依存性の注入)とは?
IoC(Inversion of Cotnrol = 制御の反転)の一種。
IoCの中にはServiceLocator
というのもあるらしいがそれはこちらを参照して欲しい。
DIとは、Dependency Injectionの略で、Springの全ての土台となっている。
DIを簡単に説明するならば、インスタンスの管理である。
##インスタンスの管理とは...?
- DIコンテナの中で、クラスをnewしてインスタンスを生成する。
- DIの中で毎回newしたインスタンスをアプリケーションに渡すのか、一度newしたインスタンスをアプリケーションに渡すのかを管理する。
- サーブレットのリクエストスコープやセッションスコープにインスタンスを簡単に登録し実装できる。
##依存性の注入とは...? DIは何をしてくれるの?
DIにおける「依存性」と「注入」の意味はこちらを参照。
「依存性の注入」と言う言葉が良くわからなければ、「オブジェクトの注入」と変えても良い。
DIは上の二つを同時に、簡単に行うが、他にも色々とある。
###DIの管理対象クラスを探す(コンポーネントスキャン)
Springを起動すると、コンポーネントスキャンという処理が走り、DIで管理する以下のような対象のアノテーションが付けられているクラスを探す。
@Component
@Controller
@Service
@Repository
@Configuration
@RestController
@ControllerAdvice
@ManagedBean
@Named
###インスタンスの生成と注入
DIコンテナに登録されたBeanのインスタンス生成と注入をする。
DI対象のクラス(Bean)を集めた後は、それらのインスタンスをDIが生成(new)する。
そして、生成したインスタンスを以下の方法で注入(代入)する。
###注入(インジェクション)方法
・フィールドインジェクション(非推奨になった)
フィールドインジェクションはフィールド(メンバ変数)に@Autowired
をつけるだけでインスタンスが注入(代入)される。
@Autowired
private SampleService sampleService;
しかし以下のようなデメリットがあるため、非推奨となった...
- テストコード作成時にDIコンテナを使わないとモックが出来ない
- フィールドにfinal属性を付与出来ずimmutableにならない
よって今では、以下のコンストラクタインジェクションを使う。
・コンストラクタインジェクション
@Service
@Transactional
public class SampleService {
private final SampleRepository sampleRepository;
// spring4.3以降はコンストラクタが1件の場合@Autowiredを省略できる
@Autowired
public SampleService(SampleRepository sampleRepository) {
this.sampleRepository = sampleRepository;
}
}
もっと簡単に書くには、Lombokを活用する。
@RequiredArgsConstructor
をクラスにアノテートするだけ
@Service
@Transactional
@RequiredArgsConstructor
public class SampleService {
private final SampleRepository sampleRepository;
}
#Beanとは?
DIコンテナ上で管理するクラスのことを『Bean』 と呼ぶ。
@Conponent
や@Controller
アノテーションがついたクラスや、@Bean
と書いたメソッドでDIコンテナにクラスを登録したものを指す。
##DIの実装方法
###JavaConfigのDI実装方法
まず、DIコンテナに登録するBeanを定義する。
@Configuration
public class JavaConfig {
@Bean
public SampleComponent sampleComponent {
return new SampleComponent();
}
@Baan
public SampleController sampleService {
return new SampleService();
}
}
これで、@Bean
を付けたgetterの戻り値が、DIコンテナのBeanとして登録される。
JavaConfigではDIコンテナで管理するインスタンス分のメソッドを用意しなければならない。
しかし、インスタンスを生成する際にコンストラクタなどに渡す値などを設定できたり、本番環境と開発環境のJavaConfigクラスを切り替えたりできる。
###アノテーションベースでDI実装
アノテーションベースでは、@Component
や@Controller
などがついたクラスを、Springが自動的にDIコンテナに登録してくれる。
#DIのライフサイクル管理とは...?
ライフサイクル管理とは、インスタンスの生成(new)と破棄の管理である。
Webアプリケーションの作成でサーブレットを使う場合、インスタンスをSessionスコープやRequestスコープに登録するが、そのインスタンスがいつ破棄されるかしっかりと把握する必要性がある。
##スコープとは...?
SessionスコープやRequestスコープと言うのは、インスタンスの有効期限である。2つのスコープの有効期限は以下のように異なる。
###Sessionスコープ
ユーザーがログインしてからログアウトするまでが有効期限。例えば、ユーザーがログインしているかどうかなどの情報をSessionスコープとして持っておける。
以下のコードは自分がOAuthを使ったアプリケーションを作った時に使用したコードを引っ張ってきた。
@Data
@Component
@SessionScope
public class OAuthDTO {
private boolean isLogin;
}
###Requestスコープ
HTTPの1リクエストが有効期限。例えば、ユーザーログイン画面から、ログインボタンを押してユーザープロフィール画面に遷移するまでがRequestスコープの範囲となる。
自分はコントローラが呼ばれる前に処理されるインタセプターで使用した。
@Component
@RequiredArgsConstructor
@RequestScope
public class OAuthInterceptor extends HandlerInterceptorAdapter {
private final OAuthDTO oAuthDTO;
/** {@inheritDoc} */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws AuthenticationException {
if (!oAuthDTO.isLogin()) {
throw new AuthenticationException("ログインしてください。");
}
return true;
}
}
###そのほかのスコープ
・singleton
Spring起動時にインスタンスを1つだけ生成する。生成後は、1つのインスタンスを共有して使う。
デフォルト設定のため、@Scope
アノテーションを付けない場合は全てsingletonとなる。
・prototype
Beanを取得するたびに、毎回インスタンスが生成される
・globalSession
ポートレット環境におけるGlobalSession単位でインスタンスが生成される。
ポートレットに対応したWebアプリケーションの場合のみ使用できる。
・application
サーブレットのコンテキスト単位でインスタンスが生成される。
以上のように、@Scope
アノテーションを使用することで、簡単にインスタンスの生成と破棄ができる。
※@Component
以外にも、@Bean
や@Controller
などにも@Scope
アノテーションは利用可能
##DIで気をつけなければならないこと
###singleton
@Scope
アノテーションを付けないとインスタンスはsingletonで作成される。つまり、オブジェクトのインスタンスは1つしか作られない。
スコープは意識しておかないとバグの発生原因になりかねない。
※@Controller
、@Service
、@Repository
のスコープは通常singletonで十分
###スコープの違い
例えば、singletonスコープのインスタンスが、sessionスコープオブジェクトを持っている場合。
@Data
@Component
@SessionScope
public class OAuthDTO {
private boolean isLogin;
}
@Service
@RequiredArgsConstructor
public class OAuthService {
private final OAuthDTO oAuthDTO;
}
このようなことになってしまうと、sessionスコープを設定したBean(OAuthDTO.java)がsingletonスコープに変化してしまう。
4月28日追記
申し訳ありません。こちらの部分間違っていました
@SessionScope
はSpring4.3から使用できるようになり、元は
@Scope(value = “session”, proxyMode = ScopedProxyMode.TARGET_CLASS)
です。
で、proxyMode = ScopedProxyMode.TARGET_CLASS
がScoped Proxyと呼ばれ、これはProxyで包んだ状態でBeanをインジェクションし、インジェクションされたBeanのメソッドを呼ぶと、実際はDIコンテナからルックアップしたBeanにメソッドを委譲するものです。
つまり...
スコープの変化は発生しない。現状 scoped-proxy
によって、特に controller や interceptor では気にせず自身より狭いスコープのものも DI コンテナから受け取る実装になっていても問題ない作りになっている。
prototype等に関しては以下を参考にするのが良い。
しかし、上の例のコードが間違っていない訳ではない。
OAuthDTO.java を OAuthService.java にインジェクトすることによってServiceクラスが状態を持つものになっている。
ServiceクラスはControllerクラスからのみ呼び出されるわけではないため、スコープ以前に、HTTPリクエストなど特定のコンテキストに縛られた状態を持っていると、それが存在しない経路で実行される時に、どこからそのインスタンスを調達するか問題になる。
よって、上の例のコードではOAuthDTO.java は Controllerクラスにインジェクトするのが良い。
実際の実装法は以下を参考にすると良い。
- 4.3. セッション管理
- Spring MVC 4.0 No. 029 コンポーネントをSessionスコープにする
- Spring Beanスコープのクイックガイド
- Spring BootでHTTPセッションをあつかう3つのパターン
#ここからは良く参考にするサイトまとめ
##アノテーション
##Lombok
##コードレビューポイント