본문 바로가기
Back-end/Spring 개념

13. @Autowired의 다양한 문제 해결방법

by devraphy 2022. 2. 14.

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

댓글