【Springboot / Kotlin】アノテーションを学ぶ(DI系)
はじめに
こちらの記事でもSpringboot
のアノテーションについて書いています。
【Springboot】 アノテーションを学ぶ
今回はDI系のアノテーションについて学んだことを書いていきます。
サンプルコードはKotlin
で記述。
DIとは
Dependeny Injection: 依存性の注入
ものすごくザックリ、プログラミングでのDIに対する自分の理解を言語化すると
「利用したいオブジェクトを指定して、利用したいときにそのオブジェクトを便利に呼び出して利用する」
と言う具合でしょうか。
大変詳しい記事
ありがとうございました。
@Component
クラスに付与するとDI(ディペンデンシーインジェクション)の対象であることを表す。
そのクラスをDIコンテナが管理してくれるようになる。
サンプルコードではGreeter型
の引数が
コンストラクタに定義されているため、Greeter
の実装クラスで@Component
アノテーションが付いたクラス、
つまりGreeterImpl
がインジェクションされる。
@RestController
class GreeterController(private val greeter: Greeter) {
@GetMapping("/hello/{name}")
fun helloByService(@PathVariable("name") name: String): Message {
val message = greeter.hello(name)
return Message(message)
}
}
interface Greeter {
fun hello(name: String): String
}
@Component // DIの対象であることを表す、DIしたオブジェクトはシングルトンになる
class GreeterImpl : Greeter {
override fun hello(name: String): String = "ハロー $name"
}
http://localhost:8080/hello/Tanaka を叩く
// helloByService() の返り値
{"message":"ハロー Tanaka"}
@Component / @Repository / @Service / @Controller
@Component
@Service
@Repository
@Controller
これらのアノテーションは動きとしては基本的に同じようである。
付与されたクラスがSpringのDIコンテナに登録され、そのクラスをDIコンテナが管理してくれるようになる。
それぞれの使い分け方
-
@Controller
: MVCでコントローラー層(画面遷移制御/Serviceの呼び出し)のクラスに付与 -
@Service
: MVCでサービス層(主にビジネスロジック)のクラスに付与 -
@Repository
: MVCでデータ層(主にDBにアクセス、DB処理)のクラスに付与 -
@Component
: MVCに限らず、DIで利用したいクラスへ付与する
これらのアノテーションについて詳しい記事
ありがとうございました。
@Component、@Service、@Repository、@Controllerの違いについて
Spring Frameworkの@Componentと@Controller,@Service,@Repositoryの違いを調べてみる
@Autowired
注入されるフィールドに付ける。
DIコンテナはAutowired
アノテーションを付けたフィールドに対して、合致するオブジェクトを探してインジェクションしてくれる。
※ lateinit var
で定義
このフィールドへのインジェクションは、変数の読み込みと同時に初期化されるのではなく、あとからインジェクションされるためvar
定義しておく必要がある。
// @Component のサンプルコードから コンストラクタを削除して、フィールドを追加
@RestController
class GreeterController {
@Autowired
private lateinit var greeter: Greeter
// 以下 @Component のサンプルと同じ
http://localhost:8080/hello/Tanaka を叩く
// helloByService() の返り値
{"message":"ハロー Tanaka"}
@Qualifier
インジェクション対象に対して、@Qualifier
というアノテーションを使い、インジェクション対象の名前を指定する。
1つのインターフェースに対して複数の実装クラスが存在する場合などにどのクラスをインジェクションするのかを明示するように使う。
サンプルコードを見た方がわかりやすいと思う。
@RestController
class GreeterController {
@Autowired
@Qualifier("JP") // JPGreeterImpl を指定
private lateinit var greeter: Greeter
@GetMapping("/hello/{name}")
fun helloByService(@PathVariable("name") name: String): Message {
val message = greeter.hello(name)
return Message(message)
}
}
interface Greeter {
fun hello(name: String): String
}
// interface Greeter の実装クラスが JP と US 2つ存在する
@Component("JP")
class JPGreeterImpl : Greeter {
override fun hello(name: String): String = "ハロー $name"
}
@Component("US")
class USGreeterImpl : Greeter {
override fun hello(name: String): String = "Hello $name"
}
http://localhost:8080/hello/Tanaka を叩く
// helloByService() の返り値
{"message":"ハロー Tanaka"}
インジェクション対象を US に変更して実行してみる
class GreeterController {
@Autowired
@Qualifier("US") // USGreeterImpl を指定
private lateinit var greeter: Greeter
// 省略
http://localhost:8080/hello/Tanaka を叩く
// helloByService() の返り値
{"message":"Hello Tanaka"}
ここのサンプルではフィールドインジェクション(@Autowired
)を使って説明したが、フィールドインジェクション、セッターインジェクションを使う場合も同様。
@Bean
付与した関数で返却したインスタンスがDIコンテナに登録され、そのインスタンスをDIで使用できるようになる
※@Bean
は@Configuration
を付与したクラス内で利用する
@Configuration
class SecurityConfig {
// 返り値をDIコンテナに登録
@Bean
fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
}
class Encode {
// DI された passwordEncoder を利用できる
@Autowired
lateinit var passwordEncoder: PasswordEncoder
}
最後に
DIはSpringboot
に限らずプログラミングでは利用されていることですのでもっと理解を深めたい。
ありがとうございました。
Discussion