0. 개요
- 이전 포스팅에서 @Autowired를 이용한 의존성 자동 주입(DI)에 대해서 알아보았다.
- @Autowired의 핵심은 Spring Container에서 관리하는 Bean을 이용한 관계 형성이다.
- 의존성을 명시할 때 의존 대상의 인터페이스(= 타입)를 명시하여, 해당 인터페이스를 상속받은 Bean을 찾는다.
- 그러나 이 과정에서 다양한 문제가 발생할 수 있다. 이에 대한 해결방법을 알아보자.
1. Bean이 존재하지 않는 경우
- Bean이 존재하지 않아도 동작해야 할 때가 있다.
- 하지만 @Autowired를 사용하면 주입할 Bean이 존재하지 않는 경우, NoSuchBeanDefinitionException이 발생한다.
- 어떻게 처리할까?
a) @Autowired(required = false)
- required 옵션은 의존 대상의 Bean이 존재하지 않는 경우, 의존성을 형성하지 않는다.
- 즉, Bean이 존재하지 않으면 해당 메서드 자체가 실행(= 호출)되지 않는다.
- Spring은 @Autowired의 default 값으로 required = true를 사용한다.
- 그러므로 Container에 명시된 타입의 Bean이 존재하는 경우에만 의존성을 형성하는 것을 기본값으로 한다.
@Autowired(required = false)
public void setBean1(NoBeanRepository noBeanRepository) {}
b) @Nullable
- Bean이 존재하지 않는 경우, 이를 null 값으로 처리한다. (= null 값을 허용한다)
- @Nullable을 사용하면 의존관계는 형성되지 않지만, 메서드의 로직은 그대로 실행된다.
- 메서드 로직을 실행 시, null 값으로 인한 오류는 발생하지 않는다.
@Autowired
public void setBean2(@Nullable NoBeanRepository noBeanRepository) {
System.out.println("setBean2 = " + noBeanRepository); // null 반환
}
c) Optional <T>
- Java 8의 Optional을 사용한 방법이다.
- Bean이 존재하지 않는 경우, 해당 객체를 Optional.empty로 처리한다.
- null 값을 허용하며, 메서드의 로직은 그대로 실행된다.
- 메서드 로직을 실행 시, null 값으로 인한 오류는 발생하지 않는다.
@Autowired
public void setBean3(Optional<NoBeanRepository> noBeanRepository) {
System.out.println("setBean3 = " + noBeanRepository); // Optional.empty 반환
}
d) 알아두자!
- 생성자를 이용한 DI 주입 방식은 반드시 Bean을 할당받아야만 동작한다.
- 그러므로 @Autowired(required = false)는 생성자 주입에서 사용할 수 없다.
- 반면에, @Nullable과 Optional은 생성자 주입에서도 사용할 수 있다.
- 생성자 주입에서 특정 필드에 null 값을 허용하고 싶은 경우, 사용할 수 있다.
e) Java8 - Optional <T> 기본개념
- 자바에서 null은 "존재하지 않는 값", "없는 값", "값이 없음"을 표현하는 변수다.
- 이 null 값을 취급하는 새로운 방법으로 등장한 것이 Java 8의 Optional이다.
- Optional은 null 값일 수도 있는 객체를 감싸는 Wrapper 클래스로, null 값을 담을 수 있는 그릇이다.
- Optional을 사용하면 다음과 같은 장점이 있다.
→ null 값을 직접 상대하지 않아도 된다. 즉, null 값을 직접 처리하지 않아도 된다.
→ null 값에 대한 처리를 직접 하지 않아도 된다.
→ 명시적으로 해당 객체가 null 값을 가질 수도 있다는 가능성을 표현할 수 있다.
2. 동일한 타입의 Bean이 2개 이상인 경우
- @Autowired는 의존관계를 형성하기 위해 Spring Container에서 인터페이스를 상속받은 Bean을 검색한다.
- Spring Container에 동일한 인터페이스를 상속받은 Bean이 2개 이상인 경우,
NoUniqueBeanDefinitionException이 발생한다.
- 이에 대한 처리방법을 알아보자.
a) 필드명 매칭
- 생성자의 매개변수로 의존 대상을 명시할 때, 구현체의 이름을 직접 지정하는 방식을 사용할 수 있다.
@Autowired
public testServiceImpl(TestRepository bean1Repository) {}
// 인터페이스 구현체 이름
b) @Qualifier
- @Qualifier는 추가적인 구분자를 명시하는 것으로, 일종의 이름표를 부착하는 방식이다.
- @Qualifier를 사용하기 위해서는 반드시 구현체의 클래스와 해당 구현체와 DI 주입이 명시되는 메서드에 부착해야 한다.
// 1. 구현 클래스에 명시
@Component
@Qualifier("mainRepository")
public class Bean1Repository implements TestRepository{}
// 2. DI 생성자에 명시
@Autowired
public TestServiceImpl(@Qualifier("mainRepository") TestRepository testRepository){}
c) @Primary
- 동일한 타입의 Bean이 2개 이상인 경우, @Primary가 붙은 Bean을 우선적으로 선택한다.
- @Primary는 구현 클래스에 명시한다.
@Component
@Primary
public class Bean1Repository implements TestRepository{}
@Component
public class Bean2Repository implements TestRepository{}
// ==> TestRepository 타입의 의존관계가 형성될 때, Bean1Repository가 우선적으로 선택된다.
d) @Qualifier와 @Primary
- 두 어노테이션을 모두 사용하는 경우, @Qualifier가 우선권을 가져간다.
- 특정한 Bean을 명시하였으므로 @Qualifier가 우선권을 갖는다고 생각하면 이해하기 편하다.
- 더불어, Spring은 항상 디테일한 설정을 필요로 하는 기능에 우선권을 부여하므로 이를 기반에 두고 생각하자.
'Back-end > Spring 개념' 카테고리의 다른 글
15. Bean 생명주기 콜백 (0) | 2022.02.16 |
---|---|
14. Annotation을 만드는 방법 (0) | 2022.02.15 |
12. Dependency Injection 방법 (0) | 2022.02.11 |
11. Dependency Injection 기본개념 (0) | 2022.02.10 |
10. Bean 자동 vs 수동 등록 (0) | 2022.02.09 |
댓글