@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;
}
}
이렇게 생성자 위에 @AutoWired를 붙이면 자동으로 의존관계 주입이 완료된다.
생성자 주입에는 중요한 특징이 하나 있는데, 호출 시점에 단 한 번만 실행된다는 것이다. 당연하지만, 생성자는 일반적인 메소드처럼 호출하여 사용할 수 없다! 즉, 누군가가 실행 중 이 의존관계를 변경할 수 없다는 것을 의미한다.
생성자 주입은 불변하거나, 필수적인 의존관계를 주입해야 할 경우 사용한다고 할 수 있다.
그리고 대부분의 경우 실행 중, 동적으로 의존 관계를 변경할 일은 거의 없다. 그렇기에 특수한 경우가 아니라면, 밑에서 언급할 다른 자동 관계 주입 보다도 이 생성자 주입을 권장한다.
💡AutoWired 생략
생성자가 유일한 경우에는 @AutoWired를 생략해도 자동으로 주입된다.
💡생성자 주입을 권장하는 또 다른 이유
1. 테스트 중 컴파일 오류 테스트 시 컴파일 오류를 일으켜 이 클래스에 어떤 의존관계가 필요한지 직관적으로 알 수 있다. 하지만 수정자 주입을 사용할 경우에는 클래스 생성 시 컴파일 오류를 일으키지 않고, 실행했을 때 Null Pointer Exception 오류를 발생시킨다.
생성자 주입의 경우
수정자 주입의 경우
2. final 키워드 final 키워드를 사용할 수 있게 된다. final 키워드가 사용되었을 경우, 실수로 값을 설정하지 않았더라도 컴파일 오류를 일으켜 잘못 되었음을 바로 알려준다. 수정자를 사용할 경우에는 final 키워드를 사용하지 못한다.
수정자 주입
수정자, 즉, Setter를 이용해 의존관계를 주입하는 방법이다.
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@AutoWired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@AutoWired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
이렇게 setXXX라는 이름의 수정자 메서드에 @AutoWired를 붙여 의존관계 주입을 할 수 있다.
생성자와 달리 수정자는 개발자가 호출하여 사용할 수 있으므로, 선택, 변경 가능성이 있는 의존관계에 주로 사용한다. 하지만, 생성자 주입에서 언급했다시피 실행 중 동적으로 의존 관계를 변경할 일은 거의 없다.
게다가 수정자 주입을 사용하게 될 경우, 개발자가 임의로 변경해서는 안 되는 의존관계를 변경해버리는 참사가 발생할 수 있다.
필드 주입
필드에 바로 @AutoWired를 붙여 의존관계를 주입하는 방법이다.
@Component
public class OrderServiceImpl implements OrderService {
@AutoWired
private MemberRepository memberRepository;
@AutoWired
private DiscountPolicy discountPolicy;
}
코드 자체가 매우 간결해져 많이 사용할 것 같지만, 실제로는 권장하지 않는 방식이다.
이렇게 사용하면 테스트를 하기 어렵다. 보통 테스트 코드는 스프링을 사용하지 않고 순수한 자바 코드를 이용하는 것이 좋다.
@Test
void fieldInjectionTest() {
OrderServiceImpl orderService = new OrderService();
// 의존관계를 주입할 방법이 없다.
orderService.createOrder(1L, "itemA", 10000);
}
이렇게 OrderService에 테스트하고 싶은 의존관계를 주입하여 활용하고 싶지만, 개발자가 원하는데로 의존관계를 주입할 수 있는 방법이 없다.
void fieldInjectionTest() {
OrderServiceImpl orderService = new OrderServiceImpl(new MemoryMemberRepository, new fixDiscountPolicy());
}
생성자 주입이나 수정자 주입이었다면, 이렇게 원하는 객체를 선언하여 주입 후 테스트할 수 있었을 것이다.
하지만 필드 주입을 하면 이렇게 순수한 자바코드로만 테스트를 할 수 있는 방법이 사라진다. 결국 테스트를 위해 클래스 내부에 수정자를 따로 선언하여 사용하게 되는데, 이럴 거면 애초에 수정자 주입을 사용하는 것이 낫지 않겠는가?
그리고 위에서 언급했듯 대부분의 경우 생성자 주입을 권장하기 때문에, 결국 생성자 주입을 사용하는 것이 낫다는 결론으로 귀결된다.
일반 메서드 주입
일반적으로는 잘 사용하지 않지만, 일반 메서드에도 의존관계를 주입할 수 있다.
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@AutoWired
public init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
하지만 생성자 주입, 수정자 주입을 통해 대부분의 상황을 커버할 수 있으므로 잘 사용하지 않는다.
옵션 처리
주입할 스프링 빈이 컨테이너에 존재하지 않는 경우에도 동작해야할 때가 있다. 변수에 null 값이 들어가 있어도 직접적으로 사용하지 않는다면 동작하는 것처럼 의존 관계도 이러한 경우가 있는 것이다.
💡 생성자 주입에 이 옵션을 적용하면 어떻게 되나요?
기술적으로는 가능하지만, 생성자 주입의 경우에는 이 옵션이 권장되지 않는다.
위에서 말했듯, 생성자 주입은 불변, 필수적인 의존관계를 주입할 때 사용한다. 그런데 이 옵션을 주면 의존성이 없을 때 null 값이 들어갈 수 있게 된다. 필수라고 했는데 이렇게 설정한다는 것 자체가 논리적으로 이상하다. 또 생성자는 객체가 생성될 때 한 번 호출된다. 이때 의존성을 주입하지 않았다는 것은 필요한 의존성이 없다는 의미가 되는데, 이 또한 논리적으로 맞지 않다.
애초에 이 옵션을 사용하고 싶다면 수정자 주입을 이용하면 된다. 권장되지 않는다 라고 하지만, 사실 사용하면 안 된다에 가깝다.
이러한 옵션 처리 방법에는 세 가지가 존재한다.
@AutoWired(required = false)
이렇게 @AutoWried 애노테이션에서 required 옵션을 false로 설정하는 방법이다. 이렇게 설정을 할 경우, 자동 주입할 대상이 없으면 메서드 자체가 호출이 되지 않는다.