Spring/스프링 핵심 원리 - 기본편

[의존 관계 자동 주입] 생성자 주입 선택하기

alsruds 2023. 12. 18. 16:59
🧐강의🧐
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

  • 다양한 의존 관계 주입 방법
    • 생성자 주입
    • 수정자 주입
    • 필드 주입 (권장 X)
    • 일반 메서드 주입

 

  • 항상 생성자 주입 선택 + 필요 시 수정자 주입 선택

 

1️⃣ 생성자 주입

☑️ 생성자를 통해 의존 관계 주입하기

 

· ⭐ 특징

     - 생성자 호출 시점에 한 번만 호출

     - "불변, 필수" 의존 관계에 사용

          - 불변 : 바뀌지 않는 코드

          - 필수 : final 변수 → 대응값 존재해야 함

     - ⭐ 생성자가 1개만 있으면, @Autowired 생략 가능 (스프링 빈만 해당)

 

· OrderServiceImpl

...
@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;
    }

    ...
}

 

2️⃣ 수정자 주입

☑️ 수정자 메서드 (setter 필드 값 변경) 를 통해 의존 관계 주입하기

 

· 특징

     - "선택, 변경" 가능성이 있는 의존 관계에 사용

          - 선택 : @Autowired(required = false)

          - 변경 : 외부 호출

 

· OrderServiceImpl

...
@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;
    }
	
    ...
}

 

 참고
- @Autowired 는 주입할 대상이 없으면 오류 발생 → @Autowired(required = false) 지정
- 자바빈 프로퍼티, 자바에서는 setXxx, getXxx 메서드를 통해 값을 읽거나 수정 (필드값 변경X)

 

3️⃣ 필드 주입

☑️ 필드에 바로 의존 관계 주입하기

 

· 특징

     - 간결한 코드

     - 외부에서 변경 불가 → 테스트하기 힘든 환경

     - DI 컨테이너가 없으면 동작 불가

     - 사용하는 경우 : 애플리케이션의 실제 코드와 관계 없는 테스트 코드, @Configuration (스프링 설정)

 

· OrderServiceImpl

...
@Component
public class OrderServiceImpl implements OrderService{

    @Autowired private MemberRepository memberRepository;
    @Autowired private DiscountPolicy discountPolicy;

    ...
}

 

참고
- @Autowired 는 @SpringBootTest 처럼 스프링 컨테이너를 테스트에 통합한 경우에만 사용 가능

 

4️⃣ 일반 메서드 주입

☑️ 일반 메서드를 통해 의존 관계 주입하기

 

· 특징

     - 한 번에 여러 필드 주입 가능

     - 일반적으로 잘 사용하지 않음

 

· OrderServiceImpl

...
@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;
    }

    ...
}

 

참고
- 스프링 컨테이너가 관리하는 스프링 빈이어야 의존 관계가 자동으로 주입됨

🚨 문제 상황 🚨

수정자 의존 관계인 경우

 

· OrderServiceImpl

...
@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;
    }

    ...
}

 

· OrderServiceImplTest

class OrderServiceImplTest {
    @Test
    void createOrder() {
        OrderServiceImpl orderService = new OrderServiceImpl();
        Order order = orderService.createOrder(1L, "itemA", 10000);
    }
}

 

· 테스트 결과

➡️ NPE (Null Point Exception) 발생 : memberRepository, discountPolicy 모두 의존 관계 누락

 

💡 해결 방법 💡

생성자 주입 & final 키워드

 

· OrderServiceImpl

☑️ 생성자 주입 사용 시 final 키워드 사용 : 생성자에 값이 설정되지 않을 시, 컴파일 시점에 오류 방지

...
@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;
    }

    ...
}

 

· OrderServiceImplTest

...
class OrderServiceImplTest {

    @Test
    void createOrder() {
        MemoryMemberRepository memberRepository = new MemoryMemberRepository();
        memberRepository.save(new Member(1L, "name", Grade.VIP));

        OrderServiceImpl orderService = new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
        Order order = orderService.createOrder(1L, "itemA", 10000);

        org.assertj.core.api.Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}

 

· 테스트 결과

 

참고
- 생성자 주입을 제외한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드 사용 불가
- 수정자 주입 사용 시, setXxx 메서드를 public 으로 열어두어야 함 → 좋은 설계 방법 X
- 생성자 주입 사용 시, 객체를 생성할 때 1번만 호출되므로 불변하게 설계 가능