스프링 사용자들이 선호하는 주입 방식

Dong Wook Lee (Michael)·2021년 1월 31일
0

스프링에서 객체를 주입하는 방법에 대해서, 검색을 해보았다. 그랬더니 잘 정리가 된 글들을 볼 수 있었다.
우선 스프링에서 객체를 주입하는 방법은 3가지가 있다.

  • 생성자 주입(Constructor Injection)
  • 필드 주입 (Field Injection)
  • 수정자 주입 (setter Injection)

내가 처음에 사용했던 주입 방식은 바로 필드 주입 방식이였다.
객체 주입 방식중에서 가장 간단하다 하지만 스프링 사용자들이 선호하는 주입 방식이 아니고 어떤 방식을 선호하는지 찾아보라고 하셨다.
검색을 해보니 선호하는 주입 방식은 바로 생성자 주입 방식이다. 그렇다면 왜 사람들은 생성자 주입 방식을 다른 방식보다 선호할까?
이유는 아래 블로그에서 찾을 수 있었다.

순환 참조를 방지할 수 있다.

개발을 하다보면, 여러 컴포넌트간에 의존성이 생기는데 그중에서 A가 B를 참조하고, B가 다시 A를 참조하는 상황이 바로 순환 참조이다. 순환 참조가 일어났을 때 어떤 일이 일어나는지는 아래 블로그에 잘 정리가 되어있지만 직접 실험을 해보는 차원에서 로컬 환경에서 실행을 해보았다.

@Service
public class AService {
  @Autowired
  BService bService;

  public void testA() {
    bService.testB();
  }

}
@Service
public class BService {

  @Autowired
  private AService aService;

   public void testB() {
     aService.testA();
   }

}
@SpringBootApplication
public class App implements CommandLineRunner {

    @Autowired
    private AService serviceA;

    @Autowired
    private BService serviceB;

    @Override
    public void run(String... args) throws Exception {
       serviceA.testA();
       serviceB.testB();
    }

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

위의 코드는 각각 서로의 객체를 주입 받은 다음에 서로의 메서드를 호출하고 있다.
이렇게 되면 어떤 결과가 나오게 될까? 예상 했듯이 서로의 메서드를 미친듯이 호출하다가 서버가 다운된다.

그렇다면 이를 생성자 주입 방식으로 변경해주면 어떻게 될까?

생성자 주입 방식을 사용했을 때 놀랍게도, 시작하기 전에 실패를 시켜주고 다음과 같은 로그를 남겨준다.

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

실행 결과에 차이가 발생하는 이유는 생성자 주입 방법은 필드 주입이나 수정자 주입과는 빈을 주입하는 순서가 다르기 때문이라고 한다.
먼저 수정자 주입을 살펴보면 우선 주입 받으려는 빈의 생성자를 호출하여 빈을 찾거나 빈 팩토리에 등록한다. 그 후에 생성자 인자에 사용하는 빈을 찾거나 만든다. 그 이후에 주입하려는 빈 객체의 수정자를 호출하여 주입한다.

마지막으로 생성자 주입은 생성자로 객체를 생성하는 시점에 필요한 빈을 주입한다. 조금 더 자세히 살펴보면, 먼저 생성자의 인자에 사용되는 빈을 찾거나 빈 팩터리에서 만든다. 그 후에 찾은 인자 빈으로 주입하려는 빈의 생성자를 호출한다. 즉 먼저 빈을 생성하지 않는다!!

그렇기 때문에 순환 참조는 생성자 주입에서만 문제가 된다. 객체 생성 시점에 빈을 주입하기 때문에, 서로 참조하는 객체가 생성되지 않은 상태에서 그 빈을 참조하기 때문에 오류가 발생한다.

따라서 생성자 주입 방식을 사용하는 것이 이러한 순환참조 오류를 사전에 방지할 수 있다.

테스트에 용이하다

생성자 주입을 이용하면, 테스트 코드를 조금 더 편리하게 작성할 수 도 있다.

코드를 클린하게 유지해준다

생성자 주입을 사용하는 경우는 생성자의 인자가 많아짐에 따라서, 복잡한 코드가 됨을 알 수 있고, 리펙토링 하여 역할을 분리하는 등과 같은 코드의 품질을 높이는 활동의 필요성을 더 쉽게 알 수 있다.

불변성

필드 주입과 수정자 주입은 해당 필드를 final로 선언할 수 없다. 따라서 초기화 후에 빈 객체가 변경될 수 있지만, 생성자 주입의 경우는 다르다. 필드를 final로 선언할 수 있어 런타임 환경에서 객체를 변경하는 실수를 사전에 예방할 수 있다.

공식 문서를 통한 검증

코드숨 과정을 진행하면서 무조건 블로그에서 배웠던 것이나, 들었던 내용을 공식문서에서 찾아보자는 목표를 세웠기 때문에 여기서 끝내지 않고, 공식 문서를 찾아보았다.

순간 문서를 보자마자 조금 울렁거리기는 했지만, 음성 번역기를 계속 돌려가면서 읽어보려고 시도를 해보았다... 문서랑 친해지고 싶기 때문이다.

  • 요약을 해보자면, DI는 객체가 생성자의 인자나, 펙토리 메서드의 인자 의해서 정의되는것을 말한다.
  • 또한 객체의 인스턴스가 설정된 후 펙토리 메서드에 의해서 값이 전달된다.
  • 서비스 로케이터 패턴을 사용한다.

  • 생성자 기반 DI는 컨테이너가 다수의 파라미터를 가진 생성자를 호출함으로서 이루어진다.
  • 생성자에 있는 각각의 파라미터는 의존성을 나타낸다.
  • 스태틱 펙토리 메서드를 생성자에 있는 각 인자에서 대해서 호출하여 생성하는 것과 거의 동일하다.

  • 수정자 방식의 DI는 컨테이너가 수정자를 호출함으로써 수행된다. (수정자 방식의 DI는 인자가 없는 생성자가 먼저 호출되고 나서 호출된다.)
  • 따라서 수정자 방식은 생성자가 먼저 호출된 후에 셋터가 호출된다는 것이다.

의존성 해결 프로세스

  • ApplicationContext 는 생성 및 초기화한다. 모든 설정을 읽어서... 이러한 설정 방식에는 XML, 어노테이션, JAVA 방식이 있다.

  • 각각의 Bean은 나타내어진다. 속성값, 생성자의 인자, 또는 스테틱 펙토리 메서드의 인자에 의해서..

  • 이러한 의존성은 실제로 빈이 생성될 때, 생성된다.

  • 스프링 컨테이너는 각각의 빈들이 생성될 때 검사한다.

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-basics

셀프 테스트

1. 생성자 주입 방식 vs (필드 주입, 수정자 주입 방식)을 빈을 주입하는 순서를 중점적으로 차이를 서술하라.
2. 다음중 불변 객체로 선언할 수 있는 주입 방식은? 그리고 그 이유를 서술하라.

출처

profile
오픈소스 메인테이너를 꿈꾸는 개발자!

0개의 댓글