생성자를 통해서 의존 관계를 주입 받는 방법이다.
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
생성자가 딱 1개만 있으면
@Autowired를 생략해도 자동 주입 된다.
setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
자바에서는 과거부터 필드의 값을 직접 변경하지 않고, setXxx, getXxx 라는 메서드를 통해서 값을 읽거나 수정하는 규칙을 만들었는데 그것이 자바빈 프로퍼티 규약이다.
필드에 바로 주입하는 방법이다.
@Configuration 같은 곳에서만 특별한 용도로 사용한다.@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private final MemberRepository memberRepository;
}
사용하지 말자
메서드를 통해서 주입 받을 수 있다.
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
@Autowired
public void init(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
어쩌면 당연한 이야기이지만 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다. 스프링 빈이 아닌 클래스에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다.
주입할 스프링 빈이 없어도 동작해야 할 때가 있다. 그런데 @Autowired만 사용하면 required 옵션의 기본값이 true로 되어 있어서 자동 주입 대상이 없으면 오류가 발생한다.
@Autowired(required=false)자동 주입할 대상이 없으면 setter 메서드 자체가 호출이 안된다.
@Autowired(required = false)
public void setNoBean1(Member member) {
...
}
org.springframework.lang.@Nullable자동 주입할 대상이 없으면 null이 입력된다.
@Autowired
public void setNoBean2(@Nullable Member member) {
...
}
Optional<>자동 주입할 대상이 없으면 Optional.empty가 입력된다.
@Autowired
public void setNoBean3(Optional<Member> member) {
...
}
과거에는 setter 주입과 필드 주입을 많이 사용했지만 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 그 이유는 다음과 같다.
public으로 열어두어야 한다.@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
}
@Autowired가 프레임워크 안에서 동작할 때는 의존관계가 없으면 오류가 발생하지만 위와 같이 프레임워크 없이 순수한 자바 코드로만 단위 테스트를 수행하고 있는 경우를 생각해보자. 주입 데이터를 누락했을 경우에 setter 주입의 경우에 실행은 되지만 NPE가 발생한다. 반면 생성자 주입을 사용할 경우 컴파일 오류가 발생한다.
final 키워드생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
컴파일 오류는 세상에서 가장 빠르고 좋은 오류이다.
막상 개발을 해보면 대부분이 다 불변이고 그래서 필드에 final 키워드를 사용하게 된다. 그런데 생성자도 만들어야 하고, 주입 받은 값을 대입하는 코드도 만들어야 한다.
귀찮다.
필드 주입처럼 좀 편리하게 사용하는 방법이 있다. 롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다. (코드에는 보이지 않지만 실제 호출 가능하다.)
@Autowired를 생략할 수 있다.최근에는 생성자를 딱 1개 두고 @Autowired를 생략하는 방법을 주로 사용한다. 여기에 Lombok 라이브러리의 @RequiredArgsConstructor 함께 사용하면 기능은 다 제공하면서 코드는 깔끔하게 사용할 수 있다.
@Autowired 는 타입으로 조회한다. 타입으로 조회하면 선택된 빈이 2개 이상일 때 문제가 발생한다. 아래와 같이 DiscountPolicy의 하위 타입인 FixDiscountPolicy, RateDiscountPolicy 둘 다 스프링 빈으로 선언되어있고
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
다음과 같이 의존관계 자동 주입을 실행하면
@Autowired
private DiscountPolicy discountPolicy
NoUniqueBeanDefinitionException 오류가 발생한다.
이때 하위 타입으로 지정할 수 도 있지만 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어진다. 그리고 이름만 다르고 완전히 똑같은 타입의 스프링 빈이 2개 있을 때 해결이 안된다.
스프링 빈을 수동 등록해서 문제를 해결해도 되지만, 의존 관계 자동 주입에서 해결하는 여러 방법이 있다.
@Autowired@Autowired
private DiscountPolicy rateDiscountPolicy
필드 명이 rateDiscountPolicy이므로 정상 주입된다.
필드 명 매칭은 먼저 타입 매칭을 시도 하고 그 결과에 여러 빈이 있을 때 추가로 동작하는 기능이다.
@Qualifier빈 등록시 @Qualifier를 붙여준다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
주입시에 @Qualifier를 붙여주고 등록한 이름을 적어준다.
@Autowired
public OrderServiceImpl(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Primary@Primary는 우선순위를 정하는 방법이다. @Autowired 시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
rateDiscountPolicy가 우선권을 가지도록 하자.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
아래 코드를 실행해보면 문제 없이 @Primary가 잘 동작하는 것을 확인할 수 있다.
@Autowired
public OrderServiceImpl(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Qualifier의 단점은 주입 받을 때 모든 코드에 @Qualifier를 붙여주어야 한다는 점이다. 반면에 @Primary는 그럴 필요가 없다.
의도적으로 해당 타입의 스프링 빈이 다 필요한 경우도 있다.
@Service
public class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
}
}
위와 같이 Map 또는 List로 DiscountPolicy를 받으면 RateDiscountPolicy , FixDiscountPolicy 둘 다 저장된다.