의존관계 주입에는 4가지 방법이 있다.
- 생성자 주입
- 수정자 주입(setter 주입)
- 필드 주입
- 일반 메서드 주입
- 생성자 주입
생성자를 통해서 의존 관계를 주입하는 방법이다.
생성자 호출 시점에서 딱 한번만 호출된다.
불변, 필수 의존관계에 사용된다는데 어떤 의미인지 설명을 들으며 이해해보자.
@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;
}
}
만약 여기 코드에서 Setter 등으로 해당 필드들을 변경하면 어떨까? 대참사다.
그렇기에 final로 막아놓은것이고.(불변)
생성자를 통해서 하므로 필수적으로 넣을 수 있다.
만약 생성자가 딱 하나라면? @Autowired를 생략해도 자동으로 주입된다.
만약 생성자가 여러 개라면 컴파일러에게 무엇을 Autowired해줘야하는지 알려줘야한다.
- 수정자 주입(setter 주입)
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;
}
}
만약 @Autowired 에서 주입할 대상이 없다면 오류가 발생하겠지.
자바에서의 setXXX, getXXX 메서드를 통해서 값을 읽거나 수정하는 걸 우리는 스터디에서 이미 배웠으니 패스.
- 필드 주입
이름 그대로 필드에 바로 주입하는 방법이다.
아무래도 코드가 간결하다보니 편해보이지만, 외부에서 변경이 불가해서 테스트하기 힘들다.
그래서 사용하지 않는게 맞다. 테스트코드나 스프링 설정을 하려고 할때에만 특별히 사용하는 게 좋아보인다.
@Component
public class OrderServiceImpl implements OrderService{
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
@Bean
OrderService orderService(MemberRepository memberRepository, DiscountPolicy discountPolicy){
new OrderServiceImpl(memberRepository, discountPolicy)
}
@Bean을 이렇게 활요해서 파라미터에 의존관계를 자동 주입할 수 있다.
- 일반 메서드 주입
일반 메서드를 통해 주입받는 방법이다.
한 번에 여러 필드를 주입 받을 수 있다.
하지만 일반적으로 잘 사용하진 않는다.
@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;
}
}
그리고 놓칠 만한 부분.. 의존관계 자동 주입(Autowired)는 스프링 컨테이너가 관리하는 스프링 빈이어야 작동한다.
그냥 스프링 빈이 아닌 단순한 클래스에서 @Autowired해봤자 아무 일이 일어나지 않는다.
*옵션 처리
주입할 스프링 빈이 없어도 작동시켜야할 때가 있다.
그런데 @Autowired의 경우 required = true 이므로, 주입 대상이 없으면 오류가 난다.
따라서 이 자동 주입 대상을 옵션으로 처리하는 방법들이 있다.
- @Autowired(required = false) : 자동 주입할 대상이 없으면, setter 메서드 자체가 호출이 안됨
- org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력
- Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력
// 호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member){
}
// null 호출
@Autowired
public void setNoBean2(@Nullable Member member){
}
// Optional.empty 호출
@Autowired
public void setNoBean3(Optional<Member> member){
}
그래서 출력 결과를 보면 setNoBean1은 출력이 아예 되지 않는다. required=false이기 때문에 setter 메소드 자체가 호출이 안되는 것이다.
* 생성자 주입 선택
과거엔 수정자(setter)와 필드 주입을 주로 사용하였지만
최근엔 스프링을 포함한 프레임워크 대부분이 생성자 주입을 권한다. 그 이유는
<불변>
- 대부분의 의존관계 주입이 한 번 일어나면 애플리케이션 종료 시점까지 의존관계를 변경할 일이 없다.
오히려 애플리케이션 종료 전까지 의존관계가 변하면 안된다.(불변) - 수정자 주입을 사용하려면 setXXX메소드를 public으로 해야하는데, 이렇게 되면 누군가가 실수로 변경할 수도 있고
변경하면 안되는 메소드를 public으로 열어둔 것이기 때문에 좋은 설계가 아님. - 생성자 주입을 통해 불변을 실현할 수 있다.
<누락>
- 만약 수정자 의존관계인 경우를 생각해보자.
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;
}
...
}
이제 테스트를 해보자.
@Test
void createOrder(){
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L, "itemA", 10000);
}
실행 결과는 Null Point Exception이다. 왜냐하면 memberRepository와 discountPolicy의 의존관계 주입이 모두 누락되었기 때문이다.
final 키워드
생성자 주입을 사용하면 필드에 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;
}
}
필수 필드인 discountPolicy에 값을 설정해야 하는데 누락이 되었다, 그러면 final 키워드로 인해 컴파일 시점에서
오류가 난다.
생성자 주입 방식만 final 키워드를 사용할 수 있다.
나머지 주입 방식은 생성자 이후에 호출되니까 사용할 수 없다.
정리)
생성자 주입 방식을 선택하는 이유는 프레임워크에 의존하지 않고 순수한 자바 언어의 특징을 잘 살리는 방법이다.
기본적으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에 수정자 주입 방식을 옵션으로 부여하면 된다.
항상 생성자 주입을 선택하고, 가끔 옵션이 필요하면 수정자 주입을 선택하자.
필드 주입은 사용하지 말자..
*롬복(Lombok)과 최신 트렌드
필드 주입처럼 편리하게 사용하는 방법이 없을지 고민하다 나온 방법이다.
우선 어떻게 도입하게 되었는지 이해해보자.
@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를 생략할 수 있다 했으므로 생략해도 된다.
롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능은 final이 붙은 필드들을 모아서 생성자를 자동으로 만들어준다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
롬복 라이브러리 설치 과정은 생략. 구글링을 하자.
* 조회 빈이 2개 이상 - 문제
@Autowired는 타입(type)으로 조회한다 했다. 그러다보니,
@Autowired
private DiscountPolicy discountPolicy
// ac.getBean(DiscountPolicy.class)
위 두개의 코드는 기능이 유사하게 동작한다.
타입으로 조회하면 선택된 빈이 두 개 이상일 때 문제가 생긴다.
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
@Autowired
private DiscountPolicy discountPolicy
이렇게 하면 오류가 나겠지 당연히.
이를 해결 하는 방법은 여러가지가 있다.
<조회 대상 빈이 2개 이상일 때 해결 방법>
- @Autowired 필드 명 매칭
- @Qualifer - > @Qualifer끼리 매칭 -> 빈 이름 매칭
- @Primary 사용
- @Autowired 필드 명 매칭
@Autowired는 타입 매칭을 시도하고, 여러 빈이 있다면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
아래 코드와 같이 기존 코드에서 필드 명이 빈 이름으로 변경된다.
@Autowired
private DiscountPolicy discountPolicy
@Autowired
private DiscountPolicy rateDiscountPolicy
- Qualifier 사용
@Qualifier는 추가 구분자를 붙여주는 방법이다. 주입 시 추가적인 방법을 제공하는 것일뿐 빈 이름을 변경하는 것은 아니다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
* 생성자 자동 주입 예시
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy
}
* 수정자 자동 주입 예시
@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
- @Primary 사용
@Primary 는 우선 순위를 정하는 방법이다. @Autowired시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
//생성자
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
//수정자
@Autowired
public DiscountPolicy setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Qualifier의 단점은 주입 받을 때 모든 코드에 @Qualifer를 붙여줘야 한다.
반면에 @Primary는 @Qualifier을 붙일 필요가 없다.
강의에서 예시로 든 것을 공유해보려한다.
코드에서 자주 사용하는 메인 데이터베이스의 connection을 얻는 스프링 빈이 있고
코드에서 특별한 기능으로 가끔 사용하는 서브 데이터베이스의 connection을 얻는 스프링 빈이 있을 때
메인 데이터베이스를 connection 하는건 @Primary를 적용해서 편리하게 조회하는 것이 유리하다.
서브 데이터베이스 connection 하는건 @Qualifier을 이용해 명시적으로 획득하는 방식으로 사용하면 좋다.
@Primary는 기본값처럼 동작하고, @Qualifier는 사용자 설정 느낌이 강하다.
이런 경우, 스프링은 자동보다는 수동이, 넓은 범위보단 좁은 범위의 선택권이 우선 순위가 높다.
그렇기에, @Qualifier이 우선권이 높다.
<Annotation 직접 만들기>
@Qualifier("mainDiscountPolicy") 이렇게 문자열을 적으면 컴파일시 타입 체크가 안된다.
이를 방지하기 위해 직접 Annotation을 만들어서 해결하는 방법이 있다.
// 아래 @들은 Qualifer의 정의 그대로 가져온것
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}
//생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
//수정자 자동 주입
@Autowired
public DiscountPolicy setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
'스프링 강의 필기 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
강의 수강 후 전반적인 정리 - 1편 (0) | 2022.08.06 |
---|---|
섹션 7 - (2). 의존관계 자동 주입 (0) | 2022.07.13 |
섹션 6. 컴포넌트 스캔 (0) | 2022.07.13 |
섹션 5 - 싱글톤 컨테이너 (0) | 2022.07.09 |
섹션4 - 스프링 컨테이너와 스프링 빈 (0) | 2022.07.09 |