의존관계 주입 방법은 크게 4가지가 있다.
- 생성자 기반 의존성 주입
- 설정자 기반 의존성 주입(setter)
- 필드 기반 의존성 주입
- 일반 메서드 주입
생성자 기반 의존성 주입
컨스트럭터 인젝션이라고도 불리는데 이름 그대로 생성자를 통해서 의존관계를 주입 받는 방법이다.
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
생성자 주입은 생성자 호출 시점에 딱 1번만 호출되는 것이 보장되기 때문에,
- 주입받은 객체가 변하지 않거나 (불변)
- 반드시 필요한 객체의 주입을 강제 할 수 있다. (필수)
그리고 생성자가 1개만 있을 경우에 @Autowired를 생략해도 자동 주입이 되는 편리성을 가지고 있다.
설정자 기반 의존성 주입 (setter)
세터 인젝션 이라고도 불리며 이름 그대로 설정자 메서드를 통해 의존관계를 주입하는 방법이다.
@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;
}
}
외부에서 변경이 가능하므로
- 변경 가능성이 있는 의존관계에 사용한다.
- 자바빈 프로퍼티 규약의 설정자 메서드 방식을 사용하는 방법이다. (setXxx 식의 메서드를 생성해 필드값을 수정)
@Autowired로 주입할 대상이 없는 경우에 오류가 발생하게 된다. 주입받는 객체가 변경될 가능성이 있는 경우가 극히 드물고 변경 가능한 상태로 두면 객체의 일관성을 유지하기 어렵기 때문에 사용하지 않는게 좋으며 실제로도 거의 안쓰이고 있다.
필드 기반 의존성 주입
필드 인젝션 이라고도 불리며 이름 그대로 필드에 바로 의존관계를 주입하는 방법이다.
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
- 코드가 간결하다.
- DI 프레임워크 없이 접근할 수 없다.
외부에서 접근이 안되기 때문에 테스트 코드를 짜기 어렵다는 단점이 있다. 이 때문에 예전에 많이 쓰이던 방법이였지만 단위 테스트의 필요성이 대두된 이후로 잘 안쓰이고 있다.
애플리케이션의 실제 로직과 관계없거나 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 사용한다.
일반 메서드 주입
일반 메서드를 통해서 주입 받는 방법이다.
@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;
}
}
- 세터 인젝션과 동일한 방법이다.
- 한 번에 여러 필드를 주입 받을 수 있도록 작성할 수 있다.
수정자 주입과 방법이 동일하기 때문에 마찬가지로 거의 안쓰이는 방법이다.
생성자 주입을 선택해야 하는 이유
스프링 프레임워크 외에 DI 프레임워크 에서는 생성자 주입을 권장하고 있다. (다른 방법인 필드 인젝션을 사용하려고 할 때, IntelliJ IDEA 기준으로 'Field injection is not recommended' 라는 경고 문구가 표시되기도 한다.)
생성자 주입을 권장하는 이유를 정리해 보겠다.
1. 객체의 불변성
객체의 의존관계는 애플리케이션 종료 시점까지 변경될 일이 거의 없다. 만약 있게 된다면, 외부에서 수정의 가능성을 열어 두어서 유지보수가 힘들어 지므로 세터 인젝션이나 일반 메서드 주입을 이용하지 말고 생성자 주입을 통해 변경의 가능성을 아예 배제하고 불변성을 보장받아 유지보수하기 좋게 해야 한다. (객체 생성시 딱 1번만 호출 되므로 가능)
2. 테스트 코드의 작성
테스트가 특정 프레임워크에 의존하게 되면 테스트 비용이 증가하게 되고 유연하지 못하게 된다. 그래서 테스트 코드는 되도록 순수한 자바 코드로 작성하는데 생성자 주입을 사용하면 이것이 가능해 지며 컴파일 시점에 객체를 주입받아 테스트 코드를 작성할 수 있다. 이 때문에 주입하는 객체가 생성자에서 누락된 경우가 생기면 컴파일 시점에 에러가 발생하기 때문에 사전에 오류를 방지할 수 있다. (누락 방지)
3. final 키워드 작성
생성자 주입을 사용하면 필드 객체에 final 키워드를 사용할 수 있다.
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
/* discountPolicy 누락 */
}
}
위와 같이 final키워드를 사용하면 컴파일 시점에 누락된 의존성을 확인할 수 있다.
다른 주입 방법들은 객체가 생성된 이후 (생성자 호출 이후)에 호출되기 때문에 final 키워드를 사용할 수 없다.
결론
객체의 불변성을 확보하고 테스트 코드의 작성이 용이해지며 컴파일 오류로 위험을 사전에 방지할 수 있는 생성자 주입을 사용하자.
그리고 생성자 주입과 수정자 주입을 동시에 사용할 수 있기 때문에 옵션을 부여해야 하는 예외적 사항일 경우에는 수정자 주입을 같이 사용하면 된다.
'Backend > Spring' 카테고리의 다른 글
빈 생명 주기(Bean Life Cycle)와 콜백(callback) (0) | 2023.12.24 |
---|---|
@Autowired 빈 설정 방식 (+ @Qualifier, @Primary) (0) | 2023.12.22 |
스프링 빈 설정하는 방법 (XML, @Configuration, @Component) (1) | 2023.12.17 |
스프링의 싱글톤 (+ 싱글톤 레지스트리란?) (0) | 2023.12.11 |
DI (Dependency Injection, 의존관계 주입) 이란? (+ 필요성) (0) | 2023.12.10 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!