의존성을 주입하는 방법에는 무엇이 있는가? 각 방법의 장단점은 무엇인가? 어떤 기준으로 나눠서 사용하는가?

kdkdhoho·2023년 4월 21일
0

Spring

목록 보기
22/26

3줄 요약

  1. 방법에는 생성자 주입, setter 주입, 필드 주입이 있다.
  2. 스프링이 권장하는 방법은 생성자 주입이다.
  3. 의존성을 재설정하거나 다시 주입하는 특수한 상황이라면 setter 주입을 고려해라

스프링 공식문서에서는 2가지의 major한 방법이 있다고 합니다.

1. 생성자 주입

생성자 주입은, 각 종속성을 나타내는 다양한 파라미터를 사용하여 생성자를 호출하는 컨테이너에 의해 수행됩니다.

특정 파라미터를 받는 정적 팩터리 메서드를 호출하여 빈을 생성하는 것 또한, 위와 거의 동일합니다.

방법

public class WebController {

    private final GameService gameService;

    @Autowired
    public WebController(final GameService gameService) {
        this.gameService = gameService;
    }
}

위와 같이 생성자를 통해, 객체의 인스턴스 생성과 동시에 의존성이 주입되어 생성됩니다.

이때 @Autowired 애노테이션을 통해 스프링에게 자동으로 GameService 타입의 Bean을 주입받도록 요청합니다.

장점

필드에 final 키워드를 붙일 수 있습니다. 즉, 객체의 불변성을 보장할 수 있습니다.

단점

객체 생성 이후에 의존성을 변경하지 못합니다.

하지만 이런 일은 거~의 없거나 아예 없다고 알고 있습니다.

주의

그런데 만약 GameService에 대한 구현체를 여러 가지로 구현하고, 바꿔 끼워주고 싶다면 어떻게 할까요?
동일한 타입에 대한 빈이 2개 이상이라면 스프링은 어떤 빈을 주입해줄까요?

우선 Service라는 인터페이스를 만들고, 그 아래에 GameServiceTestService 라는 구현체가 존재한다고 가정해봅시다.
그리고 WebControllerService를 의존받고 생성자에서 Service 타입을 주입받도록 하면 인텔리제이가 아래 사진처럼 경고합니다.

그리고 인텔리제이가 제안하는 @Qualifier를 이용하여 아래 코드처럼 직접 명시해주어 선택적으로 주입받을 수 있습니다.

@Autowired
    public WebController(@Qualifier("gameService") final Service service) {
        this.service = service;
        new AnnotationConfigApplicationContext(WebController.class);
    }

2. Setter 주입

setter 주입은 컨테이너가 기본 생성자 또는 파라미터가 없는 정적 팩토리 메서드를 호출하여 빈을 인스턴스화한 후 빈의 세터 메서드를 호출하여 수행됩니다.

방법

흔히 우리가 getter/setter할 때의 setter를 생각하면 됩니다.

객체 내의 필드에 대해 set을 열어주고 @Autowired를 붙여주어 스프링에게 의존성을 주입받습니다.

@Autowired
public void setService(final Service service) {
    this.service = service;
}

여기서 @Autowired를 붙이지 않으면 순수 자바 코드가 되고, 따라서 스프링은 setter 주입하지 않습니다.

그런데 한 가지 궁금한 것이 생겼습니다.
기본 생성자, 파라미터로 의존성을 주입받는 생성자, setter 주입이 공존하면 우선순위가 어떻게 되는 지가 궁금해졌습니다.

public class WebController {

    private Service service;

    public WebController() {
        System.out.println("기본 생성자");
    }

    @Autowired
    public WebController(final Service service) {
        System.out.println("생성자 주입");
        this.service = service;
    }

    @Autowired
    public void setService(@Qualifier("testService") final Service service) {
        System.out.println("setter 주입");
        this.service = service;
    }

결과는 아래와 같이 나왔습니다.

역시 공식문서에 나와있는 것처럼 기본 생성자 호출 후, setter 주입을 한다는 것을 확인할 수 있었습니다.

그렇다면 또 궁금한 것이 생겼습니다. 여기서 기본 생성자가 없다면?

신기하게도 생성자 주입이 먼저 이뤄지고 나서, setter 주입이 이루어지는 것을 확인할 수 있습니다.

장점

의존성을 언제 어디서든 유연하게 설정할 수 있습니다.

따라서 단위 테스트 시, 유용할 수 있습니다.

단점

객체의 의존성이 바뀐다면, 객체가 전혀 예상치 못한 행동을 할 수 있습니다.

또한 set메서드를 외부에서 호출하여 null을 넣어줄 수 있는 가능성이 있기에 매우 위험합니다.

또, 위의 실험에서 보듯이 생성자 주입과 setter 주입만 있다면, 마지막에 호출되는 것은 setter 주입이 됩니다.
따라서 개발자의 의도와 다르게 주입이 될 가능성이 있습니다.

3. 필드 주입

마지막으로 필드 주입에 대해 알아보겠습니다.

방법

public class WebController {

    @Qualifier("testService")
    @Autowired
    private Service service;
 }

위 코드처럼 객체 내 필드에 직접 @Autowired를 붙여주어 주입받습니다.
이 또한 @Qualifier를 이용해 명시해줄 수 있습니다.

장점

코드가 간결해집니다.

단점

final 키워드를 붙이지 못합니다.

외부에서 변경할 수 있는 방법이 없습니다.

프레임워크에 지나치게 의존한다.

그러면 어떤 기준으로 나눠서 사용하는가?

가장 기본적으로 생성자 주입 방법을 사용한다.

하지만, 의존성을 변경할 필요가 있다거나 외부에서 의존성을 바꿔줄 필요가 있다면 setter 주입 방법을 사용한다.

또, 테스트 시에는 생성자와 setter가 불필요하기에 필드 주입으로 간결하게 사용한다.

스프링이 권장하는 방법

생성자 주입과 setter 주입을 혼합할 수 있으므로, 필수 종속성에는 생성자 주입을, 선택적 종속성에는 setter 주입을 권장합니다.

Spring은 일반적으로 생성자 주입을 옹호합니다. 이는 애플리케이션 컴포넌트를 불변 객체로 구현하고 필요한 종속성이 null이 되지 않도록 보장할 수 있기 때문입니다.
또 생성자로 주입된 컴포넌트는 항상 완전히 초기화된 상태로 클라이언트에 반환됩니다.

setter 주입은 주로 클래스 내에서 합리적인 기본값을 할당할 수 있는 선택적 종속성에서만 사용해야 합니다. 그렇지 않으면 코드가 종속성을 사용하는 모든 곳에서 null을 체크해야 합니다.
setter 주입의 한 가지 이점은, 해당 클래스의 객체를 나중에 재구성하거나 다시 주입할 수 있다는 것입니다.
JMX MBeans가 훌륭한 예입니다.

스프링에서도 생성자 주입을 default로 사용하고, 만약 해당 클래스의 의존성을 재구성하거나 다시 주입한다는 특수한 상황이 온다면 setter 주입을 사용하라고 하네요.

참고

https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/beans.html#beans-constructor-injection

profile
newBlog == https://kdkdhoho.github.io

0개의 댓글