Back-end/Spring 개념

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

devraphy 2022. 2. 14. 20:57

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은 항상 디테일한 설정을 필요로 하는 기능에 우선권을 부여하므로 이를 기반에 두고 생각하자.