의존관계 주입은 크게 4가지 방법이 있습니다.
이름 그대로 생성자를 통해서 의존 관계를 주입 받는 방법입니다.
지금까지 우리가 진행했던 방법이 바로 생성자 주입입니다.
@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;
}
...
OrederService의 구현체에 들어가보면 생성자에 @Autowired라고 되어 있습니다.
스프링에서 컴포넌트 스캔을 하면 OrderServiceImpl이 스프링 빈에 등록이 되고 등록이 된다면 생성자를 호출해야 합니다.
생성자를 호출할 때 @Autowired가 있다고 하면 스프링 컨테이너에서 MemberRepository 타입의 스프링 빈과 DiscountPolicy 타입의 스프링 빈을 꺼내서 주입해 줍니다.
→ 컴포넌트 스캔과 의존관계 자동 주입 시작하기에서 자세하게 설명했습니다.
[중요!] 특징
생성자 호출시점에 딱 1번만 호출되는 것이 보장됩니다.
1번만 호출하는 것의 장점은 한 번만 세팅을 하고 그 다음부터는 코드만 잘 짜면 세팅 못하게 막을 수 있다는 것 입니다.
불변, 필수 의존관계에 사용
"항상 그런 것은 아니고 주로 불변, 필수 의존관계에 사용한다고 이해하면 됩니다."
생성자는 2번 호출이 안됩니다. (즉, 강제로 수정하는 메서드를 만들지 않는 이상 불변 입니다.) 개발에서 불변이란 것은 굉장히 중요합니다. 좋은 개발 습관은 한계점과 제약이 있는 것 입니다.
위의 코드를 다시 봅시다.
생성자를 통해서만 의존관계가 주입이 되고 외부에 있는 어느 누구도 memberRepository를 수정할 수 없습니다.
discountPolicy도 애플리케이션 조립되는 시점에 딱 한번 들어오고 discountPolicy에 대한 인스턴스를 변경할 수 있는 방법이 없습니다.
이렇게 하는 의도가 뭐냐면 처음에 Configuration으로 스프링 컨테이너에 빌딩되서 올라갈 때 연관 관계에 대한 그림을 다 그려서 끝내고 싶은 것 입니다.
공연을 띄우기 전에 배우를 다 정해서 끝내고 싶은 것 입니다. 공연하는 중간에 배우를 바꿀 일이 없다는 겁니다.
즉, 임의로 아래와 같이 수정하는 메서드를 만들면 안됩니다. 이런 것을 public으로 열어두면 안됩니다.
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
그래서 memberRepository, discountPolicy는 불변입니다.
불변이라는 특징은 정말 중요합니다.
수정하는 메서들을 만들어 놓으면 누군가는 바꿉니다.. 이러면 정말 찾기 어려운 버그가 나옵니다.
값을 세팅하고 더 이상 값을 바꾸지 않을 거라면 가급적 생성자에 값을 넣고 수정자 메서드 (setter 메서드)를 만들지 않으면 됩니다.
private final은 무조건 값이 있어야 한다는 의미입니다.
즉, 생성자로 들어오는 것이 값이 꼭 있어야 한다고 지정하는 것 입니다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
위 처럼 memberRepository를 빼버리면 컴파일 오류가 발생합니다.
언어적으로 잡은 것 입니다.
외부에서 호출을 할 때도 (MemberRepository memberRepository, DiscountPolicy discountPolicy) 처럼 생성자에 값이 있으면 관례상 왠만하면 null 호출을 안합니다.
생성자가 있으면 값을 다 넣어야 하고 문서에서 null을 허용한다는 말이 없는 이상 생성자에는 값을 다 채워 넣어야 한다고 생각하면 됩니다.
그래서 필수 값에 사용합니다.
[중요!] 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입 됩니다. 물론 스프링 빈에만 해당합니다.
생성자가 2개 있다는 것은 아래 코드와 같습니다.
public OrderServiceImpl() {
}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
이 때는 @Autowired를 지정해야 합니다.
스프링 컨테이너가 OrderServiceImpl를 호출 할 때 어떤 생성자를 @Autowired로 호출해야할지 모르기 때문입니다.
그래서 생성자가 1개라면 @Autowired가 없어도 있는 것과 같은 효과를 부여합니다. 생략되어 있다고 생각하면 됩니다. (@Bean이나 @Component 둘 다 동일합니다.)
한 번 확인을 해봅시다.
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
System.out.println("memberRepository = " + memberRepository);
System.out.println("discountPolicy = " + discountPolicy);
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
이렇게 코드를 작성하고 이전에 만들었던 AutoAppConfigTest를 돌려봅시다.
만약 생성자 호출이 될 때 스프링 컨테이너에서 스프링 빈이 들어오면 NULL이 아니라 값이 있을 것 입니다.
실행해봅니다.
@Autowired가 없어도 값이 들어있는 것을 알 수 있습니다.
즉, 스프링 빈일 때 생성자가 1개라면 자동으로 @Autowired가 적용이 됩니다.
요즘에는 이렇게 생략해서 사용합니다.
setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법입니다.
보통 필드의 값을 수정할 때 관례상 아래 코드처럼 "set + 필드명(맨 앞 대문자)"을 사용합니다.
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
이 것을 수정자라고 합니다.