[Spring Basic] 의존관계 주입
1. 생성자를 통해 의존관계를 주입하는 경우는 지금까지 사용해 온 방법이고, 가장 많이 사용되는 방법이다.
생성자를 호출하는 시점에만 주입됨을 보장할 수 있고, 때문에 변하면 안 되는 요소와 값이 필수로 주입돼야 하는 요소에 사용한다.
@Component를 찾고 빈으로 등록할 때 생성자를 호출하게 되기 때문에 빈의 등록과 의존관계의 주입이 함께 발생한다고 생각해도 좋다.
생성자가 하나만 존재할 경우 @Autowired 애너테이션을 생략해도 자동으로 주입된다.
2. 수정자를 통해 의존관계를 주입하는 경우는 두 번째로 많이 사용되는 방법이고, 변경될 가능성이 높고 선택적으로 주입받아도 괜찮은 요소에 사용한다.
생성자를 통해 의존관계를 주입하는 경우와 다르게 빈을 등록하는 작업과 의존관계가 주입되는 작업이 독립되어있다고 생각할 수 있다.
@Autowired는 주입할 대상이 없으면 오류를 뱉는데, 주입할 대상이 없어도 작동하도록 하려면 @Autowired(reqired = false)로 작성해주면 된다.
3. 필드를 통해 의존관계를 주입하는 경우는 거의 안 쓴다..
변수 앞에 @Autowired를 붙여 사용하고, 외부에서 변경할 수 없어져서 테스트하기 힘들어지는 단점 등 여러 단점이 있어 잘 사용하지 않는다.
테스트나 스프링 설정 파일에서는 사용해도 큰 문제가 없으니 괜찮지만, 애플리케이션 코드에는 사용하지 말자.
4. 일반 메서드를 통해 의존관계를 주입하는 경우는 Setter를 통해서 의존관계를 주입하는 경우와 비슷하다.
때문에 생성자와 Setter에 비해 많이 사용되지 않는다.
@Autowired를 사용할 때 주입할 스프링 빈이 없어도 동작하도록 해야 할 때가 있다.
@Autowired의 기본값은 reqired = true로 설정되어있어, 자동 주입 대상이 없으면 오류를 뱉는다.
이를 해결하기 위한 세 가지 방법이 있다.
1. reqired = false로 바꾸기
2. @Nullable 활용해서 주입할 대상이 없으면 null로 처리
3. Optional<> 로 감싸주기
@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);
}
대부분의 의존관계 주입은 한번 실행되고 나서는 애플리케이션이 끝날 때 까지 의존관계를 바꿀 필요가 없다.
수정자 주입이나 메서드 주입으로 의존관계를 주입하게 되면 해당 요소가 변할 수 있는 가능성을 열어 두게 돼 바람직한 설계에서 벗어나게 된다.
이 외에도 생성자 주입이 주는 여러 이점이 있으니.. 웬만하면 생성자 주입 + final 을 사용하자.
Lombok 라이브러리를 사용하면 생성자를 통한 의존관계 주입을 좀 더 쉽게 할 수 있다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
롬복이 제공하는 @RequiredArgsConstructor 애너테이션을 사용하면 final이 붙은 필드를 모아서 해당하는 생성자를 알아서 만들어준다.
롬복은 자바의 애너테이션 프로세서를 사용해 컴파일 시 코드를 생성해 주는 방식으로 동작해 코드에서는 만들어진 생성자가 보이지 않지만, 호출할 수는 있다.
최근에는 생성자를 하나만 두고 @Autowired 애너테이션을 생략하는 방향으로 코드를 작성한다.
이런 상황에서 롬복을 사용한다?
생성자도 롬복이 알아서 만들고 애너테이션도 스프링이 알아서 붙여줘서 아주 편해진다..
@Autowired로 의존관계를 주입할 때는 타입을 기준으로 검색하는데, 해당하는 타입이 두 개 이상인 경우는 오류가 발생한다. 어떻게 해결해야 할까?
가장 간단한 해결책으로는 수동으로 빈을 추가해 줄 수 있다. (수동은 자동보다 우선순위가 높다)
자동을 유지하면서 해결하는 방법으로는 크게 세 가지가 있다.
1. @Autowired의 필드 명 매칭
2. @Qualifier 사용
3. @Primary 사용
1)
@Autowired는 기본적으로 타입 매칭을 시도하고, 해당하는 빈이 여러 개일 경우 필드 이름, 파라미터 이름으로 추가 매칭을 시도한다. (이름 중 빈 이름과 일치하는 요소가 있으면 그거로 결정함)
두 번째 시도에도 일치하는 요소가 없으면 오류를 뱉는다.
2)
@Qualifier는 추가 구분자를 붙여준다. (빈 이름을 변경하는건 아님)
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("name_name")DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Component
@Qualifier("name_name")
public class RateDiscountPolicy implements DiscountPolicy{
}
이런 느낌이다.
@Qualifier에 해당하는 요소를 찾지 못하면, 설정한 구분자와 일치하는 스프링 빈을 찾지만.. 구분자를 추가하는 용도로만 생각하자.
3)
해당하는 요소가 여러 개 있으면 @Primary 애너테이션이 붙은 요소가 우선시된다.
깔끔하게 처리할 수 있어 선호되는 방식이다.
주로 사용하는 연결은 Primary를, 보조로 사용하는 연결은 Qualifier를 붙여 명확하게 표시해주면 좋다.
다른 접근으로 중복되는 빈들을 모두 가져와서 어떤 빈을 선택할지 동적으로 결정할 수도 있다.
List와 Map을 사용한다.
public class AllBeanTest {
@Test
void findAllBean() {
ApplicationContext ac = new
AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1L, "userA", Grade.VIP);
int discountPrice = discountService.discount(member, 10000,
"fixDiscountPolicy");
assertThat(discountService).isInstanceOf(DiscountService.class);
assertThat(discountPrice).isEqualTo(1000);
}
static 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;
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
System.out.println("discountCode = " + discountCode);
System.out.println("discountPolicy = " + discountPolicy);
return discountPolicy.discount(member, price);
}
}
}
DiscountService 클래스는 DiscountPolicy를 주입받는다. (구현체가 여러개라 Map과 List로 주입받음)
discount메서드를 통해 인자로 들어온 DiscountPolicy의 구현체 중 하나를 Map에서 찾고 반환해준다.
그런데 자동으로 등록하는게 수동으로 등록하는거보다 훨씬 편한데.. 수동은 언제 써야 할까?
자동으로 등록해도 객체지향 설계의 원칙을 위배하지도 않고 최근 트렌드도 자동 기반의 컴포넌트 스캔 방식으로 빈을 등록하는거지만, 수동 등록에 비해 문제가 발생했을 때 찾기 힘들다.
때문에 비즈니스 로직같이 제대로 적용되고 있는지 확인하기 어려운 로직을 구현할 때는 수동 등록 방법을 사용하는게 합리적이다.
즉, 자동 등록을 기본으로 사용하되 위와 같이 예외 상황에서 수동 등록을 사용하자.
'Spring > Spring' 카테고리의 다른 글
[Spring Basic] 로그 (0) | 2022.08.16 |
---|---|
[Spring Basic] 빈의 생명주기와 스코프 (0) | 2022.08.09 |
[Spring Basic] Singleton (0) | 2022.08.07 |
[Spring Basic] 스프링과 IoC (0) | 2022.08.06 |
[Spring Basic] JPA와 AOP (0) | 2022.07.29 |
댓글
이 글 공유하기
다른 글
-
[Spring Basic] 로그
[Spring Basic] 로그
2022.08.16 -
[Spring Basic] 빈의 생명주기와 스코프
[Spring Basic] 빈의 생명주기와 스코프
2022.08.09 -
[Spring Basic] Singleton
[Spring Basic] Singleton
2022.08.07 -
[Spring Basic] 스프링과 IoC
[Spring Basic] 스프링과 IoC
2022.08.06