의존관계 주입 방법은 여러가지가 있다.
제일 먼저 지난 시간까지 사용했던 생성자를 통한 의존관계 주입이다.
이름 그대로 생성자를 통해서 의존관계를 주입 받는 방법이고, 생성자 호출시점에 딱 1번만 호출되는 것이 보장되며, 주로 불변,필수 의존관계에 사용한다.
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Autowired
애너테이션을 통해 생성자를 통해 의존관계를 주입한다고 명시해주면 생성자 주입 방식을 사용할 수 있다.
(생성자가 딱 1개만 있다면 @Autowired
를 생략해도 자동 주입된다. 단, 스프링 빈에만 해당된다.)
필드의 값을 변경하는 수정자 메서드를(ex. setter()
) 통한 의존관계 주입 방식이다.
선택, 변경 가능성이 있는 의존관계에 사용하는게 좋다.
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy){
this.discountPolicy = discountPolicy;
}
생성자 주입 방식은 빈 등록과 동시에 의존관계 주입도 한다.
반면, 수정자 주입 방식은 빈 등록 후 의존관계 주입이 이루어진다.
💡 스프링 라이프 사이클
1. 빈 전체 등록
2. 의존관계 주입(@Autowired
)
참고로 @Autowired
의 기본 동작은 주입할 대상이 없으면 오류가 발생한다.
하지만 주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
그럴 땐 아래 3가지 방식 중 상황에 맞게 채택해서 사용하면 된다.
@Autowired(required=false)
: 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않는다.org.springframework.lang.@Nullable
: 자동 주입할 대상이 없으면 null이 입력된다.Optional<>
: 자동 주입할 대상이 없으면 Optional.empty가 입력된다.말 그대로 필드에 바로 의존관계 주입하는 방법이다.
코드가 간결하다는 장점이 있지만, 외부에서 변경이 불가능해서 테스트 하기 힘든 치명적인 단점이 존재한다.
또한, DI 프레임워크가 없으면 아무것도 할 수 없으므로 되도록 사용하지 말자!
@Configuration
같은 곳에서만 특별한 용도로 사용하는건 괜찮다.@Component
public class OrderServiceImpl implements OrderService{
@Autowired
private final MemberRepository memberRepository;
@Autowired
private final DiscountPolicy discountPolicy;
}
일반 메서드를 통해 의존관계 주입 받을 수 있으며, 한번에 여러 필드를 주입 받을 수 있다.
다만, 필드 주입과 같이 잘 사용하지 않는 방식이다.
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public init(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
결론적으로, 상황에 맞게 생성자 주입 또는 수정자 주입 방식을 채택해서 사용하면 되지만 되도록 생성자 주입 방식을 채택하자.
최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입 방식을 권장한다고 한다.
생성자 주입을 권장하는 이유는 다음과 같다.
불변
public
으로 열어두어야 한다.누락
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy){
this.discountPolicy = discountPolicy;
}
...
@Test
void createOrder(){
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L, "itemA", 10000);
}
이렇게 테스트를 실행하면 실행은 되지만 NullPointException
이 발생한다.
바로 memberRepository
와 discountPolicy
모두 의존관계 주입이 누락되었기 때문이다.
수정자 주입 방식이 아닌 생성자 주입 방식을 사용하면 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다.
그리고 누락을 피할 수 있는 또 다른 방식도 있다. 바로 final
이다.
생성자 주입을 사용하면 필드에 final
키워드를 사용할 수 있다.
그래서 생성자에서 혹시라도 값이 설정되지 않은 오류를 컴파일 시점에서 막아준다.
이러한 이유들이 불변하고 누락을 피하기 쉬운 생성자 주입 방식을 사용하는것이 권고되는 이유이다.
스프링을 처음 공부할 때 제일 편했던 필드 주입만 사용했던 적이 있다.
굳이 다른 생성자나 메서드들을 만들 필요가 없었기 때문이다. 하지만 요즘은 생성자 주입 방식을 주로 채택해 사용하고 있는데 제일 좋은 방식으로 코딩하고 있었던 내 자신이 뿌뜻해진다.
그래도 상황에 맞게 수정자 주입 방식도 사용해볼려는 노력을 해봐야겠다!