🚀

【Springboot / Kotlin】アノテーションを学ぶ(DI系)

2021/06/13に公開

はじめに

こちらの記事でも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