의존관계 주입에는 크게 4가지 방법이 있다.
final
키워드를 사용하기 때문에 null을 허용하지 않으므로 필수적이다.@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
애노테이션을 사용하면 스프링의 의존관계 주입 단계에서 자동으로 의존관계가 주입된다.@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;
}
}
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
@Autowired
를 일반 메서드에도 적용해 주입받을 수 있다.@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;
this.discountPolicy = discountPolicy;
}
@Autowired
의 required 옵션의 기본값이 true로 설정되어 있기 때문에 주입할 빈은 반드시 스프링 컨테이너에 등록이 되어있어야 한다.
주입할 스프링 빈이 없어도 다음 세 가지 방법을 사용해 작동시킬 수 있다.
@Autowired(required=false)
: 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않는다.org.springframework.lang.@Nullable
: 자동 주입할 대상이 없으면 null이 입력된다.Optional<>
: 자동 주입할 대상이 없으면 Optional.empty
가 입력된다.test폴더의 core 밑에 autowired 패키지를 생성하고, 그 밑에 AutowiredTest 클래스를 생성한다.
public class AutowiredTest {
@Test
void AutowiredOption(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
}
static class TestBean{
@Autowired(required = false)
public void setNoBean1(Member noBean1){
System.out.println("noBean1 = " + noBean1);
}
@Autowired
public void setNoBean2(@Nullable Member noBean2){
System.out.println("noBean2 = " + noBean2);
}
@Autowired
public void setNoBean1(Optional<Member> noBean3){
System.out.println("noBean3 = " + noBean3);
}
}
}
noBean2 = null
noBean3 = Optional.empty
setNoBean1은 호출되지 않았다.
final
키워드를 사용할 수 있기 때문에 생성자에서 값이 설정되지 않는 오류를 컴파일 시점에 막을 수 있다.수정자 주입을 사용하였을 때의 단점을 예시로, 어떤 클래스가 다른 어떤 클래스와 의존 관계에 있는지 확인하기 위해서는 그 클래스의 코드를 자세히 살펴보아야 하는 수고가 필요하다. 하지만 생성자 주입을 사용한다면 해당 객체를 생성할 때에 생성자 코드만 보고도 어떠한 클래스와 의존관계가 있고, 어떠한 의존관계 주입이 필요한지 쉽게 알 수 있다.
컴파일 오류가 세상에서 가장 빠르고 좋은 오류다.
Lombok 라이브러리에서 제공하는 에노테이션을 사용하면 getter/setter이나 ToString, 생성자를 자동으로 생성해 주기 때문에 코드를 간단하게 작성할 수 있다.
생성자를 1개만 두는 경우에 @Autowired
를 사용하여 생성자 코드를 직접 작성하지 않고도 @RequiredArgsConstructor
를 사용해 생성자를 자동으로 만들 수 있는 것이다.
build.gradle
파일을 수정한다.@RequiredArgsConstructor
를 사용해 다음과 같이 작성할 수 있다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
테스트를 위해 DiscountPolicy
를 상속받는 FixDiscountPolicy
와 RateDiscountPolicy
둘 다 스프링 빈으로 등록해 본다. 이 경우, DiscountPolicy
에 의존하는 다른 클래스는 의존관계 주입시 오류가 발생하게 된다. (NoUniqueBeanDefinitionException)
수동 빈 등록으로 해결할 수 있지만, 자동 빈 등록시에도 해결할 수 있는 방법이 있다.
조회 대상 빈이 2개 이상일 때에는 다음과 같이 해결할 수 있다.
@Autowired
필드 명 매칭@Quilifier
-> @Quilifier
끼리 매칭 -> 빈 이름 매칭@Primary
사용@Autowired
는 타입 매칭을 시도하고, 매칭 결과가 두 개 이상 있다면 필드 이름 또는 파라미터 이름으로 빈 이름을 추가 매칭한다.
DiscountPolicy로 조회하면 두 개의 빈이 조회되어 오류가 발생한다.
@Autowired
private final DiscountPolicy discountPolicy;
이 때, 필드 명을 구체 타입으로 지정하면 오류가 발생하지 않는다.
@Autowired
private final DiscountPolicy discountPolicy;
@Qualifier
은 추가 구분자를 제공한다. 만약 해당 이름의 Qulifier가 없다면 같은 이름의 빈 이름으로 조회한다. Qualifier는 추가 구분자를 제공하는 것으로, 빈 이름을 변경하는 것이 아님을 기억한다.
다음 예시는 mainDiscountPolicy라는 이름으로 구분자를 추가하는 예시이다.
빈 등록시 @Quilifier
는 다음과 같이 사용한다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy{
의존관계 주입 시 @Quilifier
는 다음과 같이 사용한다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
의존관계 주입 시에 여러 빈이 매칭된다면 @Primary
애노테이션이 있는 빈이 우선권을 갖는다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{
@Primary와 @Qualifier모두 존재할 때에는 좀 더 상세하게 지정한 @Qualifier
가 우선권을 갖는다. 따라서 일반적으로 사용하는 클래스는 @Primary
를 사용하고, 특별한 경우에 사용할 때에만 @Qualifier
를 사용하는 것이 바람직하다.
생략
해당 타입의 스프링 빈이 모두 필요한 경우에는 다음과 같이 생성자 주입을 할 수 있다.
public class DiscountService{
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
@Autowired
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
}
}
다형성을 유지하며 동적인 빈 주입이 필요할 때 사용한다.
DiscountService의 메서드로 다음과 같은 예시가 있다.
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member, price);
}
discount메서드는 원하는 DiscountPolicy 빈을 사용하기 위해 discountCode를 파라미터로 받는다. 이를 통해 상황에 맞게 동적으로 빈을 사용할 수 있다.
일반적으로 편리한 자동 빈 등록을 사용하는 것이 좋다. 하지만 애플리케이션이 큰 영향을 끼치는 부분이나 다형성이 중요한 부분에서는 수동 등록을 고려해 볼 수 있다.
본 포스팅은 김영한 강사의 스프링 핵심 원리 강의를 수강하고 요약한 것으로, 해당 강의의 영상 및 강의자료를 참고하였습니다.