@Autowired、依存関係注入

- 様々な方法があるが、**生成子注入にするのが一番良い。**
- 生成者がたった1つだけあれば@Autowiredを省略しても自動注入される。


## 使用方法
### 1. 生成子注入
```java
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}
```
- **最もお勧めする方法である。**
- 生成者呼び出し時点でたった1回だけ呼び出されることが保障される。
- setter関数を作成しないなどの方法により、値を一度設定した後、再び設定できないように防ぐこともできる。
- 主に不変、必須依存関係に使用するが、ほとんどの依存関係注入は、一度起きればアプリケーション終了時点まで依存関係を変更することがあってはならないため、主に使用される。

### 2. Setter注入
```java
@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
    }

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
            this.discountPolicy = discountPolicy;
    }
}
```
- setメソッドをパブリックに開いておく必要があるが、誰かが誤って変更できるメソッドを開いておくのは良い設計方法ではないため、推奨しない。
- **（ほとんどないが）選択、変更の可能性がある依存関係に使用する。**

### 3. フィールド注入
```java
@Component
public class OrderServiceImpl implements OrderService {

    @Autowired
    private MemberRepository memberRepository;

    @Autowired
    private DiscountPolicy discountPolicy;
}
```
- 推薦しない方法であり、実際に使用するとField injection is not recommendedというメッセージが表示される。
- コードが簡潔だが、外部からの変更が不可能でテストが難しい。

### 4. 一般メソッド注入
```java
@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
            this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
    }
}
```
- あまり使わない。
- 一度に複数のフィールドを注入してもらうことができる。


## 注入するSpring Beanがなくても動作させたい場合

- **- @Autowiredはrequiredオプションのデフォルト値がtrueになっており、自動注入対象がないとエラーが発生する。**

- 注入するSpring Beanがなくても動作させたい場合は、以下の3つの方法を使用する。
    1. @Autowired(required=false)
        - 自動注入する対象がなければ、修正子メソッド自体が呼び出されない。
    2. org.springframework.lang.@Nullable
        - 自動注入する対象がなければnullが入力される。
    3. Optional<>
        - 自動注入する対象がなければOptional。emptyが入力される。
    ![스크린샷 2023-09-16 17.24.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567846/052470fa-325e-bda6-59f3-c2543eac2864.png)

## Beanを2つ以上選びたい場合
- @Autowiredはタイプで照会するため、Beanが2つ以上選択できるが、そうするとエラー(NoUniqueBeanDefinitionException)が発生する。
    **しかし、意図的に本当に該当タイプのスプリングビーンがすべて必要な場合がある。**

- Beanを2つ以上選択したい場合は、以下の3つの方法を使用する。
    1. よく使うSpring Beanに@Primaryを付ける。
        ```java
            @Component
            @Primary
            public class RateDiscountPolicy implements DiscountPolicy{
            	...
            }
            
            @Component
            public class FixDiscountPolicy implements DiscountPolicy{
            		...
            }
        ```
    2. @Autowiredフィールド名をBeanの名前に変更する。
        - @Autowiredは最初にタイプマッチングを試み、この時、複数のBeanがいればfield名、パラメータ名で再びマッチングする。
        ```java
        @Component
        public class OrderServiceImpl implements OrderService {
        
            private final MemberRepository memberRepository;
            private final DiscountPolicy discountPolicy;
        
            @Autowired
            public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
                this.memberRepository = memberRepository;
                this.discountPolicy = rateDiscountPolicy;
            }
        ```
    3. @Qualifier、追加区分子を付ける。
        - Beanの名前を変更するのではない。
        ```java
        @Component
        @Qualifier("fixDiscountPolicy")
        public class FixDiscountPolicy implements DiscountPolicy{
        		...
        }
        
        @Component
        @Qualifier("mainDiscountPolicy")
        public class RateDiscountPolicy implements DiscountPolicy{
        		...
        }
        
        @Component
        public class OrderServiceImpl implements OrderService {
        
            private final MemberRepository memberRepository;
            private final DiscountPolicy discountPolicy;
        
            @Autowired
            public OrderServiceImpl(
        				MemberRepository memberRepository, 
        				@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
        
                this.memberRepository = memberRepository;
                this.discountPolicy = discountPolicy;
            }
        		...
        }
        ```

## 照会したBeanがすべて必要な場合
```java
public class AutoAppConfigTest {

    static class DiscountService {
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;

        public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
            this.policyMap = policyMap;
            this.policies = policies;
            System.out.println(policyMap);
            System.out.println(policies);
        }

        public int discount(Member member, int price, String discountCode) {
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            return discountPolicy.discount(member, price);
        }
    }

    @Test
    void findAllBean() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);

        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L, "userA", Grade.VIP);
        int discountPrice = discountService.discount(member,10000, "fixDiscountPolicy");

        assertThat(discountService).isInstanceOf(DiscountService.class);
        assertThat(discountPrice).isEqualTo(1000);

				discountPrice = discountService.discount(member,20000, "rateDiscountPolicy");;
        assertThat(discountPrice).isEqualTo(2000);
        

    }
}
```