daisuzz.log

Java, Kotlinを使ったSpring におけるDependency Injectionについて

概要

今回は、Java, Kotlinそれぞれを使ったSpringのDIの書き方をまとめてみます。 環境は、Java 1.8, Kotlin 1.2.31, Spring Boot 2.0.0.RELEASE, Spring 5.0.3.RELEASE です。

SpringにおけるDIについて

DIについての詳しい説明は割愛します。詳しく知りたい場合は、Springの公式リファレンスを読んでください。

SpringにおけるDIは、「Setter injection」と「Constructor injection」の主に2種類があります。ネットで調べると「Field injection」という言葉も出てきますが、公式リファレンスには出てこない用語です。説明上この記事でも、Field injectionという言葉をつかいますが、本質的にはSetter injectionと同じです。 また、Spring チームは、依存性がnon-nullであることやimmutableであることを保証できるという理由で、Constructor injectionを推奨しています。一方のSetter injectionは、依存性を再設定するケースなど、限られた用途で使うべきとされています。

以降では、これら「Field injection」, 「Setter injection」, 「Constructor injection」 それぞれについてJavaとKotlinでの書き方をまとめていきます。

Field Injection

まずは、JavaでのField Injectionについてです。 ↓のように書くことでField Injectionを表現します。

@Component
class SampleComponent{

  @Autowired
  public HogeService hogeService;

  @Autowired
  public FugaService fugaService;

  public void hogehoge(){
    hogeService.hoge();
  }

  public void fugafuga(){
    fugaService.fuga();
  }
}

次にKotlinでは↓のように書きます。

@Component
class SampleComponent{
  @Autowired
  lateinit var hogeService: HogeService
  
  @Autowired
  lateinit var fugaService: FugaService

  /* 以下メソッド省略 */
}

Kotlinのフィールド変数は、宣言時に初期化されていないといけないので、DIする場合はlateinit修飾子をつけて遅延初期化であることを明示的に示す必要があります。 また、Field Injectionでは、あとから値を代入するので、valを使った変数宣言はできません。

Setter Injection

次に、setter injectionをjavaで書くと↓のようなコードになります。

@Component
class SampleComponent{
 
  private HogeService hogeService;
  
  private FugaService fugaService;

  @Autowired
  public void setHoge(HogeService hogeService){
    this.hogeService = hogeService;
  }

  @Autowired
  public void setFuga(FugaService fugaService){
    this.fugaService = fugaService;
  }

  /* 以下メソッド省略 */
}

同様に、Kotlinでsetter injectionを書くと、↓のようなコードになります。

@Component
class SampleComponent{

  private var hogeService: HogeService? = null
    @Autowired
    set(hogeService){
      this.hogeService = hogeService
    }

  private var fugaService: FugaService? = null
    @Autowired
    set(fugaService){
      this.fugaService = fugaService
    }

  /* 以下メソッド省略 */

/* ↓のように書くこともできる
  private lateinit var hogeService: HogeService
  
  private lateinit var fugaService: FugaService
  
  @Autowired
  fun setHogeService(hogeService: HogeService){
    this.hogeService = hogeService
  }

  @Autowired
  fun setFugaService(fugaService: FugaService){
    this.fugaService = fugaService
  }
*/
}

kotlinでは、フィールドをvalで宣言するとgetterが、varで宣言するとgetterとsetterが自動で生成されます。そのため、Field injection同様変数はvarで宣言します。 さらに、setterの処理をカスタムする必要があるので、↑のコードのようにset()を書く必要があります。 また、コメントに書いたように、Javaとほとんど同じ書き方で書くこともできます。

Constructor Injection

最後に、Constructor InjectionをJavaで書くと↓のようなコードになります。

@Component 
class SampleComponent{

  private final HogeService hogeService;

  private final FugaService fugaService;

  @Autowired
  SampleComponent(HogeService hogeService, FugaService fugaService){
    this.hogeService = hogeService;
    this.fugaService = fugaService;
  }

  /* 以下メソッド省略 */
}

コンストラクタがひとつしか存在しない場合は、@Autowiredを省略することができます。

@Component 
class SampleComponent{

  private final HogeService hogeService;

  private final FugaService fugaService;

  // コンストラクタが1つしかない場合は、@Autowiredを省略できる
  SampleComponent(HogeService hogeService, FugaService fugaService){
    this.hogeService = hogeService;
    this.fugaService = fugaService;
  }
  /* 以下メソッド省略 */
}

さらに、Lombokをつかってコンストラクタを自動生成すると、コンストラクタの宣言を省略してConstructor Injectionが表現できます。

@Component 
@AllArgsConstructor
class SampleComponent{

  private final HogeService hogeService;

  private final FugaService fugaService;

  // Lombokで自動生成してくれるので、コンストラクタを書かなくていい

  /* 以下メソッド省略 */
}

次に、KotlinでConstructor Injectionを表現すると↓のコードになります。

@Component
class SampleComponent(
  private final val hogeService: HogeService,
  private final val fugaService: FugaService
){
  /* 以下メソッド省略 */
}

Kotlinでは、プライマリコンストラクタという文法があり、↑のように書くことで、コンパイル時にコンストラクタを自動で生成してくれます。これによって、LombokアノテーションをつけずにConstructor Injectionを表現することができます。

参考