의존관계 주입 방법은 크게 4가지가 있다.
setter
)@Autowired
private MemberRepository memberRepository;
(이게 된다고?)
코드가 간결해서 좋아보이지만, 외부에서 변경이 불가능해서 테스트가 어렵다
사용하지 말자
테스트 코드에서는 사용해도 무방하다.
의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 한다.
아래는 스프링 빈이 아닌 클래스를 자동 주입하려고 할 때 에러가 나지 않게 하는 작업이다.
주입할 스프링 빈이 없어도 동작하게 해야할 때가 있다.
@Autowired
에 required:true
가 되어있어서 주입대상이 없으면 오류가 난다.
자동 주입 대상을 옵션으로 처리한다.
@Autowired(required=false)
: 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨
@Nullable
: 자동 주입할 대상이 없으면 null이 입력됨
@Autowired
public void setNoBean2(@Nullable Member member) {}
Optional<>
: 자동 주입할 대상이 없으면 Optional.empty
가 입력됨@Autowired
public void setNoBean3(Optional<Member> member) {}
요즘은 생성자 주입을 권장하는 편인다.
- 의존관계 주입은 한번 일어나면 변경할 일이 잘 없다. (차라리 불변해야 한다.)
- 생성자 주입은 객체 생성시 딱 1회만 호출되기 때문에 불변되게 설계된다.
생성자 주입을 사용하면 필드에 final 키워드를 생성할 수 있다.
이 기능으로 생성자에서 값이 설정되지 않으면 컴파일 시점에 막아준다.
(웬만하면 다 final 이겠다 그럼)
롬복을 이용해서 수 많은 작업들이 줄어들게 된다. 생성자 주입도 마찬가지다.
build.gradle 파일에 롬복을 추가한다.
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
dependencies {
//lombok 라이브러리 추가 시작
compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok'
}
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
@RequiredArgsConstructor 기능을 사용하면 final 이 붙은 필드를 모아서 생성자를 자동으로 생성해준다.
@Autowired
private DiscountPolicy discountPolicy
만약 FixDiscountPolicy , RateDiscountPolicy를 둘다 스프링 빈으로 등록한다면 어떻게 될까?
=> NoUniqueBeanDefinitionException 오류가 발생한다.
하나의 빈을 기대했는데 fixDiscountPolicy , rateDiscountPolicy 2개가 발견되었다 라고 한다.
discountPolicy => rateDiscountPolicy
@Autowired
private DiscountPolicy rateDiscountPolicy
@Qualifier
는 추가 구분자를 붙여주는 방법이다.@Qualifier
끼리 매칭된다.빈 등록시에 @Quilifier
를 적어준다.
@Autowired
@Qualifier("mainDiscountPolicy")
private DiscountPolicy discountPolicy
생성자에서 주입시 @Qualifier
에 등록한 이름을 적어준다.
@Autowired
public OrderServiceImpl(
MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy
) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
- @Primary
는 우선순위를 정하는 방법이다.
@Primary
가 우선권을 갖는다.@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Qualifier("mainDiscountPolicy")
이렇게 문자를 적으면 컴파일시 타입체크가 되지 않는다.
애노테이션을 직접 만들어서 문자열을 바꿔보자
@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;
}
스프링이 나오고 시간이 갈 수록 점점 자동을 선호하는 추세다
애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 딱! 설정 정보에 바로 나타나게 하는 것이 유지보수 하기 좋다.
편리한 자동 기능을 기본으로 사용하자
직접 등록하는 기술 지원 객체는 수동 등록
다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민해보자