본문 바로가기

Spring/Core

의존관계 주입

의존관계 주입

의존관계 주입은 런타임 시점에 스프링 컨테이너에 등록된 빈을 @Autowired가 적용된 필드에 주입하여 구현체를 직접 생성하지 않아도 사용이 가능하게끔 하는 것이다. 주입받은 코드는 구현체를 직접적으로 생성하지 않았기 때문에 OCP, DIP를 지킬 수 있다.

 

의존관계 주입은 크게 생성자 주입, 수정자 주입(setter 주입), 필드 주입이 있다. 기본적으로 스프링은 빈을 먼저 등록 후 의존관계 주입을 하지만, 생성자 주입의 경우 빈 등록 전 객체를 생성하고 의존관계를 주입 후 등록하는 예외가 있다.

생성자 주입

@Component
public class OrderServiceImpl implements OrderService {
	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;

	//@Autowired 생성자가 하나이므로 @Autowired가 없어도 의존관계 주입이 된다.
	public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
		this.memberRepository = memberRepository;
		this.discountPolicy = discountPolicy;
	}
}

생성자 주입은 생성자 호출 시점에 한 번만 호출되는 것이 보장되며 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 또한 final 키워드를 사용해서 생성자에 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다. 생성자가 하나만 있을 경우 @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;
    }
}

수정자 주입은 생성자 주입과는 다르게 주로 주입받는 객체가 변경할 가능성이 있을 때 사용된다. public 으로 되어 있어 자유롭게 변경이 가능하지만 그만큼 문제가 생길 가능성도 있다. @Autowired 의 기본 동작은 주입할 대상이 없으면 오류가 발생하므로 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.

 

필드 주입

@Component
public class OrderServiceImpl implements OrderService {
    @Autowired
    private MemberRepository memberRepository;
    @Autowired
    private DiscountPolicy discountPolicy;
}

가장 간편하게 주입받을 수 있는 방법이지만 지양하는 방법이다. 그 이유는 외부에서 변경이 불가능해서 테스트 하기 힘들기 때문이다.

 

조회 대상 빈이 2개 이상일 경우

@Autowired는 타입으로 조회하기 때문에 하나의 인터페이스에 여러 구현체가 있다면 어떤 빈을 주입해야할 지 모른다. 그런 경우를 대비해서 해결할 수 있는 방법이 있다.

 

@Autowired 필드명

@Autowired
private DiscountPolicy discountPolicy;

@Autowired
private DiscountPolicy rateDiscountPolicy;

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

 

@Qualifier 사용

//빈 등록시 @Qualifier를 붙이는 방법
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy 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) {
    return discountPolicy;
}

@Qualifier 는 추가 구분자를 붙여주는 방법이다. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다. @Qualifier는 위처럼 빈 등록 시 @Qualifer를 등록해주고 생성자 자동 주입 시, 수정자 자동 주입시에 사용할 수 있다. @Qualifier 의 단점은 주입 받을 때 다음과 같이 모든 코드에 @Qualifier 를 붙여주어야 한다는 점이다. 하지만 @Primary를 사용하는 것보다 높은 우선권을 가진다.

 

@Primary 사용

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {}

두개 이상의 빈이 @Autowired 시에 여러 빈이 매칭되면 @Primary 를 적용한 빈에게 우선권을 가진다