[스프링&JPA 스터디] #7 의존관계 자동 주입 (생성자 주입), 우선순위 설정

오예찬·2023년 7월 13일
0

spring&jpa 스터디

목록 보기
7/15

의존관계 주입


생성자 주입

  • 이름 그대로 생성자를 통해서 의존 관계를 주입 받는 방법이다.
  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
  • 불변, 필수 의존관계에 사용
@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;
	 }
}
  • 중요! 생성자가 1개만 있으면 @Autowired를 생략해도 자동 주입된다. 물론 스프링 빈에만 해당한다.
@Component
public class OrderServiceImpl implements OrderService {

 	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;
    
 	public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy  discountPolicy) {
 		this.memberRepository = memberRepository;
 		this.discountPolicy = discountPolicy;
 	}
}

생성자 주입의 특징

불변

  • 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 함)
  • 수정자 주입을 사용하면, 누군가 실수로 변경할 수 있다. 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.

누락

  • 생성자 주입을 사용하면 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다. 컴파일 오류는 IDE를 통해 어떤 값을 필수로 주입해야 하는 지 바로 알 수 있다.

final 키워드

  • 생성자 주입을 사용하면 필드에 final키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않은 오류를 컴파일 시점에 막아준다.
  • 참고: 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출되므로, 필드에 final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.

옵션 처리

  • 주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
  • 그런데 @Autowired만 사용하면 required옵션의 기본값이 true로 되어 있어서 자동 주입 대상이 없으면 오류가 발생한다.

자동 주입 대상을 옵션으로 처리하는 방법은 다음과 같다.

  • @Autowired(required=false): 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨
  • org.springframework.lang.@Nullable: 자동 주입할 대상이 없으면 null이 입력된다.
  • Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.

예제 코드

//호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
	 System.out.println("setNoBean1 = " + member);
}

//null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
 	System.out.println("setNoBean2 = " + member);
}

//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
 	System.out.println("setNoBean3 = " + member);
}

조회 빈이 2개 이상

  • @Autowired는 타입(Type)으로 조회하기 때문에 선택된 빈이 2개 이상이면 문제가 발생한다.
  • 이떄 하위 타입으로 지정할 수도 있지만, 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어진다. 그리고 이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있을 때 해결이 안된다.

@Autowired 필드명, @Qualifier, @Primary

조회 대상 빈이 2개 이상일 때 해결 방법

  • @Autowired 필드명 매칭
  • @Qualifier -> @Qualifier끼리 매칭 -> 빈 이름 매칭
  • @Primary 사용

@Autowired 필드명 매칭

@Autowired는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.

기존 코드

@Autowired
private DiscountPolicy discountPolicy

필드명을 빈 이름으로 변경

@Autowired
private DiscountPolicy rateDiscountPolicy
  • 필드명이 rateDiscountPolicy이므로 정상 주입된다.
  • 필드명 매칭은 먼저 타입 매칭을 시도하고 그 결과에 여러 빈이 있을 때 추가로 동작하는 기능이다.

@Qualifier 사용

@Qualifier는 추가 구분자를 붙여주는 방법이다. 주입 시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.

빈 등록시 @Qualifier를 붙여 준다.

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
  • 주입시에 @Qualifier를 붙여주고 등록한 이름을 적어준다.

생성자 자동 주입 예시

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy  discountPolicy) {
	this.memberRepository = memberRepository;
 	this.discountPolicy = discountPolicy;
}
  • @Qualifier 로 주입할 때 @Qualifier("mainDiscountPolicy") 를 못찾으면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다. 하지만 @Qualifier@Qualifier 를 찾는 용도로만 사용하는게 명확하고 좋다.

@Primary 사용

@Primary는 우선순위를 정하는 방법이다. @Autowired 시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.

  • rateDiscountPolicy가 우선권을 가지도록 하자.
@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;
}

코드를 실행해보면 문제 없이 @Primary가 잘 동작하는 것을 확인할 수 있다.

참고

스프링 핵심 원리 - 기본편 (김영한)

profile
개발자를 그만두기 전 알아야 하는 100가지 이야기

0개의 댓글