스프링 공부 (인프런 김영한 선생님)/스프링 핵심원리

[스프링 핵심원리] 8. 의존관계 자동 주입

ProgYun. 2023. 5. 15. 07:29

1) 의존관계 주입 방법

- 생성자 주입

- 수정자 주입(Setter 방식)

- 필드 주입

- 일반 메서드 주입

 

-> 스프링이 관리하는 빈이어야만 의존관계 자동 주입이 일어난다고 이해할 것


생성자 주입

 

이름 그대로 생성자를 통해서 의존관계를 주입받는 방법이다.

한계점, 제약을 명확하게 지정하는 것이 좋은 개발인데 불변&필수 의존관계에서 사용된다는 점에서

딱 한번만 호출되는 것이 보장되기도 하기 때문에 좋은 방법에 해당한다

 

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    private final DiscountPolicy discountPolicy; // Interface에만 의존하게 됨.
    // 이 상태로만 두면 NPE -> 구체 클래스 어떻게 주입?
    
    // 이 위 코드에서 final을 통해 객체의 값이 꼭 존재해야 함을 강제한다

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository=memberRepository;
        this.discountPolicy=discountPolicy;
    }

    // DiscountPolicy만 의존하는게 아니라 FixDiscountPolicy도 의존하고 있었다!!
    // DIP 위반임. 결론적으로 정책 변경으로 인해, OrderServiceImpl의 코드 또한 변경해야함.
    // DiscountPolicy 확장해서 변경하면 클라이언트 코드에 영향을 주므로 OCP 위반

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

이때 생성자가 딱 하나만 있는 경우 @Autowired를 생략해도 자동주입이 진행된다. 물론 스프링 빈인 경우에만 해당(애노테이션)

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    
    private final DiscountPolicy discountPolicy; // Interface에만 의존하게 됨.


// 생략!
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository=memberRepository;
        this.discountPolicy=discountPolicy;
    }

    // DiscountPolicy만 의존하는게 아니라 FixDiscountPolicy도 의존하고 있었다!!
    // DIP 위반임. 결론적으로 정책 변경으로 인해, OrderServiceImpl의 코드 또한 변경해야함.
    // DiscountPolicy 확장해서 변경하면 클라이언트 코드에 영향을 주므로 OCP 위반

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

@Autowired() 내에 parameter로 설정할 수 있는 것

1) required -> 자동 주입 대상이 없는 경우 수정자 메서드 자체 호출 X (작성한 메서드 위에 작성)

2) org.springframework.lang.@Nullable : 자동 주입 대상이 없는 경우 Null이 입력 - 생성자에서도 사용 가능(특정 필드에 넣으면)

3) Optional<> : 자동 주입할 대상이 없는 경우 Optional.empty가 입력된다. - 생성자에서도 사용가능(특정 필드에 넣으면)

데이터가 없으면 NULL인데, NPE 발생 안하도록 Optional.empty를 반환함.

 

참고로 전부 수정자 자동주입 방식에서 쓴다.


2. 수정자 주입(Setter 주입)

- Setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해 의존관계를 주입한다.

 

참고로 스프링 컨테이너에서 자동 등록되는 빈의 경우 빈을 등록할때 자동 주입 시작시 생성자를 호출하는데,
생성자 주입이면 자동 등록 시점에 바로 일어난다, 자세한건 이전 노트 참고

 

- 선택, 변경이 있는 의존관계에 사용한다는 점에서 앞선 생성자 주입과 차이가 있다.

- 자바 빈 프로퍼티 규약(setXXX() 방식 선언)의 수정자 메서드 방식을 사용하는 방법이다. + 메서드 위에 @Autowired 가 필요하다

 

이 때 @Autowired의 기본 동작은 주입할 대상이 없는 경우 오류가 발생하기 때문에 required 인자를 false로 주는 경우 주입할 대상이 없어도 동작하게 할 수 있다.

 

생성자 주입은 필수값을 가급적 설정해야하는데 MemberRepository가 Bean에 등록되어있지 않은 경우에도 수정자 주입방식을 선택하면 선택적으로 등록이 가능하다.

 


3. 필드 주입

 

빠르게 정리하는 내용이기 때문에 일단 그냥 쓰지 말자

 

4. 일반 메서드 주입도 일반적으로 잘 사용하지 않는다

 


생성자 주입을 선택하자

 

- 과거에는 수정자 주입과 필드 주입을 많이 사용하였으나, 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다

-> 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존 관계를 변경할 일이 없음(불변해야함)

-> 수정자 주입을 사용하면 setXXX 메서드를 public으로 열어두어야하고 예기치 않은 변경이 생길 수 있음.

 

프레임워크 없이 자바 테스트 코드를 작성하는 경우가 많다, 이 때 컴파일단에서 오류를 발생하도록 설정할 수 있다.

 

예를들어 OrderServiceImpl만 테스트하고 싶은데 수정자 주입으로 의존관계 주입이 일어나는 경우,

테스트 코드를 돌리면 실행은 되는데 NPE가 발생한다.

 

이때 수정자 주입의 final 키워드를 통해 값의 할당을 강제하는 경우,

컴파일 시점에서 <variableName> might not have been initalized 라는 컴파일 오류를 통해 미연에 방지할 수 있다

 

=> 컴파일 오류가 가장 해결하기 쉬운 오류임을 기억하자.