[Spring]DIの定義と利用方法についてまとめてみた

## はじめに
Spring Frameworkの勉強をしている中で、DI(Dependency Injection)の定義と利用方法についてまとめたので、記事化してみました。
## ざっくり、DIとは
- インスタンスの管理を、アプリケーションではなくDIコンテナに委ねること
- SpringのDIコンテナが作成したインスタンスを"Bean"と呼ぶ
- DIのメリットとして、インスタンスの効率的な生成・管理や、依存性の解決を自動でおこなってくれることなどがある
# インジェクションの方法
- インスタンス変数の前に `@Autowired`等[^1] を付与すると、`＠Component`が付与されているクラスの中からインスタンス変数と型が一致するクラスのインスタンスを自動的にインジェクションしてくれます。

- インジェクションの方法として、下記3通りの方法があるので、以下でそれぞれについて紹介します。
    - ⑴フィールドインジェクション
    - ⑵コンストラクタインジェクション
    - ⑶セッターインジェクション

### ⑴フィールドインジェクション
  - フィールドを介してインスタンスをインジェクションします。
```
@Controller
public class CustomerEditController {
    
    @Autowired
    private Sample s;
    //(処理)
}
```

### ⑵コンストラクタインジェクション
  - コンストラクタを介してインスタンスをインジェクションします。
  - Spring Framework 4.3以降は`@Autowired`を省略可能
  - フィールドをImmutableにできます
  ```
  public class CustomerService{
   private final Sample s;
   @Autowired
   public CustomerService(Sample s){
    this.s = s;
   }
 ```

### ⑶セッターインジェクション
- セッターを介してインスタンスをインジェクションします。
```
  public class CustomerService{
   private Sample s;
   @Autowired
   public void setCustomerService(Sample s){
    this.s = s;
   };
```

### (番外編)ApplicationContextを用いたBean取得
- ApplicationContextインターフェースは、DIコンテナの実体であるBeanFactoryクラスを拡張したものであり、Bean定義ファイルを元にインスタンスを生成し、インスタンスのインジェクションを行います。
- よって、下記のようにApplicationContextの実装クラスのインスタンスを通して、DIを行うこともできます。
 ```
    ApplicationContext context = 
        new ClassPathXmlApplicationContext("META-INF/spring/beans-biz.xml");
    private Sample s = context.getBean(Sample.class);
 ```
# DI定義の方法  
   - DI定義の方法は、以下の３通りあります。
        - ①アノテーションを用いる方法
        - ②Bean定義ファイルを用いる方法
        - ③JavaConfigを用いる方法
- 下記で、各定義方法について紹介していきます。
## ①アノテーションを用いたDI
- [インジェクションの方法](#インジェクションの方法)で書いたように、DIの設定をアノテーションだけで行う方法です。
- アノテーションを用いたDIを行うためには、Bean定義ファイルに下記のようにコンポーネントスキャンを行う範囲を記載する必要があります。
```
// bean定義ファイル
  <context:component-scan base-package="sample.customer.web.controller"/>
  <context:annotation-config />
```
## ②Bean定義ファイルを用いたDI
- `<bean>`タグを用いてインジェクションのための設定を行えます。
- `MyBean`クラスの`propertyName`フィールドに`otherBean`というBeanをインジェクションする場合
```
<beans ...>
  <bean class="com.example.MyBean">
    <property name="propertyName" ref="otherBean"/>
  </bean>
</beans>
```

### `<bean>`タグのオプション
- `id`: オブジェクトを一意に定義するID
- `name`: オブジェクト名を設定
- `class`: Bean化するクラスを指定。パッケージ名+クラス名で記述します。
- `scope`: オブジェクトのscope(`singleton`,`prototype`,`request`,`session`,`application`)を指定します。`＠Scope`と同等
- `parent`: 設定を引き継ぐBean名を指定
- `lazy-init`: インスタンス化を遅らせるかどうかを指定します。`＠Lazy`と同等
- `autowire`: インジェクションの方法(`no`,`byName`,`byType`,`constractor`)を指定します。`@Autowired`や`@Resource`と同等

- また、`<bean>`タグの内側には以下のようなタグを記載できます。
  - `constructor-arg`: Beanのコンストラクタに渡す引数を指定
  - `property`: Beanのプロパティを設定
    - 例
  ```
  <bean id="myBean" class="com.example.MyBean">
    <property name="propertyName" value="propertyValue" />
    <property name="anotherPropertyName" ref="otherBean" />
  </bean>
  ```
## ③JavaConfigを用いたDI
- Bean定義ファイルをJavaで記述することができます。
- 以下は、`Bean定義ファイルを用いたDI`章で記載したbean定義をJavaConfigで記載したもの
```
@Configuration //JavaConfigであることを明示
@ComponentScan("sample.customer.biz.service") //コンポーネントの検索範囲を指定
public class BizConfig {
    @Bean
    public MyBean myBean(OtherBean　otherBean) {
        return new MyBean(otherBean);
    }
}
```
- JavaConfigでbean内で参照されている他のbeanを取得するには、以下の3つの方法があります。
  - 1.`@Bean`が付与されたメソッドの引数から取得
  - 2.`@Bean`が付与されたメソッドを呼び出して取得
  - 3.JavaConfigファイル内で`@Autowired`を用いて取得
- 以下で、各取得方法について紹介していきます。
### 例1: `@Bean`が付与されたメソッドの引数から取得
- otherBeanという名称のOtherBean型のbeanを、myBeanメソッドの引数から取得しています。( = otherBeanがbean定義されている必要があります。)
```
@Configuration //JavaConfigであることを明示
@ComponentScan("sample.customer.biz.service") //コンポーネントの検索範囲を指定
public class BizConfig {
    @Bean
    public MyBean myBean(OtherBean　otherBean) {
        return new MyBean(otherBean);
    }
}
```
### 例2: `@Bean`が付与されたメソッドを呼び出して取得する
- ※otherBean()を複数回呼び出してもインスタンスが複数生成されることはありません。(ただし、scopeがsingletonの場合)
```
@Configuration
public class BizConfig {

    @Bean
    public OtherBean　otherBean() {
        return new OtherBean();
    }

    @Bean
    public MyBean myBean() {
        return new MyBean(otherBean());
    }
}
```
### 例3: JavaConfigファイル内で`@Autowired`を用いて取得
- 実装ファイルと同様にフィールドインジェクションでbeanを取得します。
```
@Configuration
public class BizConfig {

    @Autowired
    private Otherbean otherbean

    @Bean
    public MyBean myBean() {
        return new MyBean(otherBean);
    }
}
```
[^1]:`@Autowired`以外にも。`@Resource`,`@Inject`を用いることができます。(参考: https://www.baeldung.com/spring-annotations-resource-inject-autowire)

## 最後に
 - DIの定義と利用方法ですが、パターンや設定の種類が多く難しいなと感じました。
 - 参考資料
     - 書籍:Spring入門