ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Dependency Injection의 종류 - Constructor, Setter, Field
    Spring 2021. 10. 16. 00:51

    Field Dependency Injection(@Autowired)

    @AutoWired ❓
    • Spring Framework에서 지원하는 Dependency 정의 용도의 Annotation
    • Spring 종속적이지만 정밀한 Dependency Injection이 필요한 경우 유용함
    • 해당 어노테이션을 사용해 Bean을 등록할 경우 Injection의 대상이 되는 클래스의 형식은 하나여야 한다(하지만 @Qualifier를 이용해 Injection할 Component의 대상을 지정해줄 수 있다)

@Service
public class Item {
  @Autowired
  private final Pizza pizza;
  @Autowired
  private final Burger burger;
}

@Autowired 어노테이션을 사용해 객체에 의존성을 주입하는 방식이다. 사용이 매우 간결해서 많이 이용되는 방식이다.

하지만 Intellij에서 이 방식을 사용할 경우 Field injection is not recommended 라는 경고 문구가 나타난다.

스프링 공식문서에서도 명시적 Wiring 방식보다 정확하지 않고, 예상치 못한 결과를 초래할 수 있으니 사용을 제한하라고 권유한다.


Field Injection을 권장하지 않는 이유

  1. 외부에서 변경이 불가능하다
  2. 객체 간 순환 종속성 문제가 발생할 수 있다
  3. DI 프레임워크가 동작해야 객체가 생성된다

Circular Dependencies - 순환 종속성 문제

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  memberServiceImpl defined in file [/examples/.../memberServiceImpl.class]
↑     ↓
|  ItemServiceImpl defined in file [/examples/.../ItemServiceImpl.class]
└─────┘

Bean의 생성은 잠재적으로, Bean의 의존성이 주입되고 의존성의 의존성이 주입될 때 형성된다.

따라서 종속성 간의 불일치 문제는 늦게 발견될 수 있는데, 이러한 현상이 최초 생성하거나 그 영향을 받는 Bean에게서 나타날 수 있다.

순환 종속성은 클래스 A가 생성자 주입을 통해 클래스 B의 인스턴스를 요구하고, 클래스 B가 생성자 주입을 통해 클래스 A의 인스턴스를 요구할 때 Container가 순환 참조를 탐지하고 예외를 발생시킨다.

Bean이 존재하지 않거나 순환 종속성이 존재할 경우 이를 컨테이너 로드 시간에 탐지한다. 스프링은 Bean이 실제로 생성될 때 속성을 설정하고 종속성을 주입하는데, 잘못 로딩된 컨테이너는 이후 객체를 요청할 때 예외를 발생시킬 수 있으므로 기본적으로 싱글톤 & 사전 인스턴스 전략을 사용하는 이유가 된다. 따라서 실제로 필요하기 전 미리 만들기 위해 약간의 초기 시간과 메모리를 들여 ApplicationContext가 생성될 때 구성의 문제점을 발견할 수 있다.

  • 객체 생성 시점에서 순환 종속성이 발생하는 것
  • 객체 생성 후 비즈니스 로직 상에서 순환 종속성이 발생하는 것

의 경우, 후자가 실제 개발 시 훨씬 위험한 문제를 초래하기 때문에 Constructor Injection 방식을 사용하자.


Constructor-based Dependency Injection

@Service
public class Item {
  private final Pizza pizza;
  private final Burger burger;

  public Item(Pizza pizza, Burger burger) {
    // ...
  }
}

// Lombok을 이용한 생성자 주입 방식
@Service
@RequiredArgsConstructor
public class Item {
  private final Pizza pizza;
  private final Burger burger;

  // ...
}

생성자 주입 방식의 장점

  1. Container의 생성 시점에 순환 참조 문제를 발견할 수 있다.
  2. final keyword를 이용해 객체를 immutable하게 만들 수 있다.
  3. 필요한 종속성이 null이 아닌지 체크할 수 있다.
  4. 항상 완전히 초기화된 상태에서 코드로 반환된다.
  5. 너무 많은 인자를 가진 생성자는 Code Smell을 유발한다 -> 클래스가 너무 많은 책임을 가지고 있으니 적절한 관심사 분리를 통해 인수를 분리해야 함을 경고한다

Setter-based Dependency Injection

@Service
public class Item {
  private Pizza pizza;
  private Burger burger;

  public void setPizza(Pizza pizza) {
    this.pizza = pizza;
  }

  public void setBurger(Burger burger) {
    this.burger = burger;
  }

  // ...
}

// Lombok을 이용한 세터 주입 방식
@Service
@Setter
public class Item {
  private Pizza pizza;
  private Burger burger;

  // ...
}
  • 인수가 없는 생성자를 호출 or 팩토리 메서드를 호출한 후 setter 메서드를 호출하는 컨테이너에 의해 수행
  • 필수 종속성을 생성자 주입 방식으로 의존성을 주입받고, 선택적 종속성을 세터 주입 방식으로 의존성을 주입받는 것을 권장한다.
  • 합리적인 기본값을 할당할 수 있는 선택적 종속성에만 사용해야 한다.
    • 그렇지 않은 경우 코드가 해당 의존성을 사용하는 모든 곳에서 null 검사를 수행해야 한다

참고자료