ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 DI 방법
    spring/core 2021. 5. 6. 17:00

    배경

    처음에 잘 배워서 그런지 스프링의 빈을 생성할 때 사용하는 DI방법에 대해 큰 고민을 하지 않았다. 당연히 롬복 + final + 생성자를 조합한 DI를 활용했다. 당연한 건 없다. 스프링이 제시하는 DI에는 어떤 게 있는지 그리고 왜 당연한 조합을 당연히 썼는지 생각하자.

     

     

    필드 DI

    공식문서에는 아예 소개된 방법도 아니고 인텔리제이는 필드 DI를 쓰지말라고 경고까지 한다. 그래도 필드 DI를 이용해 애플리케이션을 구동해보면 잘된다. 왜 스프링은 필드 DI를 DI방식에서 배제했을까?

     

    장점

    코드가 간결하고 의존성을 추가 또는 삭제해도 영향 받는 코드의 양이 적다.

    (관점에 따라 이건 단점이다-> 단일책임원칙 위배 가능성 up)

     

    단점

    우선 가장 큰 단점으로 필드 DI를 이용하면 반드시 스프링 컨테이너를 이용해 객체를 생성해야 한다는 점이다. 애초에 스프링을 쓰니까 스프링컨테이너가 없을 때 인스턴스 생성을 못한다는 점이 단점처럼 안 보일 수 있다. 하지만 단위테스트를 할 때를 생각해보면? 보통 @Service코드를 작성하는 부분은 정말 스프링 기술에 의존하지 않은 단위 테스트를 작성한다. 이때 임시로 생성한 클래스 A를 사용하려면 어떻게 해야할까?

    setter나 생성자 없이 A의 B를 세팅하는 방법은 없다. 또 두 가지 단점이 더 있는데 이건 수정자 DI와 공통이므로 뒤에서 소개하겠다.

     

    수정자 DI

    동작은 간단하다. 컨테이너는 A의 객체를 생성한 뒤 setter를 호출해 B를 세팅한다. 딱히 문제될 건 없어보이지만 깊게 보면 두 가지 문제를 안고 있다.

     

    단점

    우선 필드 B를 불변객체로 만들 수 없다. 대부분 빈은 한 번 만들어진 시점에서 의존관계 설정이 다시 되는 경우는 거의 없다.(경험상 아예 없다) 반면 setter를 열어두면 그 의존관계가 변경될 가능성을 열어둔 것이다.  또한 final키워드를 붙일 수도 없다. 이 경우는 필드 DI에서도 동일한 단점을 갖는다.

     

    다른 큰 단점으로 순환참조로 인한 오류가 문제가 발생하기 전에는 알 수 없다는 점이다.

    두 코드를 보면 순환참조로 문제가 발생한다는 점을 알 수 있다. 빈 A에서 hello()를 호출하면 빈 B의 hello()를 호출하고이러한 과정이 무한히 반복된다. 컴파일러는 물론 애플리케이션 로딩 시점에도 전혀 문제는 생기지 않는다. 빈 A를 이용해서 hello()를 호출한 순간 문제는 발생하고 스택오버플로우로 애플리케이션이 죽을 것이다. 서비스를 오픈하면서 겪을 수 있는 최악의 에러 중 하나인 듯하다.

     

     

    생성자 DI

    final 키워드를 붙인 생성자 DI를 활용하면 모든 문제가 깔끔하게 해결된다. 우선 필드의 불변성을 보장할 수 있다. 또한 위에서 지적한 순환참조 에러를 컴파일시점에 잡지는 못하지만 애플리케이션 로딩시점에 바로 알 수 있다.

    애플리케이션이 로딩되면서 죽어버린 걸 확인할 수 있다. 서비스 중이 아닌 로딩 시점에 에러가 발생한다면 빠르게 대처할 수 있다. 추가로 롬복을 이용해 final키워드를 붙인 필드의 생성자를 만들어주면 깔끔한 코드 효과까지 볼 수 있다.

     

    그렇다면 어떻게 생성자 DI만 이렇게 애플리케이션 로딩시점에 순환참조 에러를 알려줄 수 있을까? 수정자 DI의 원리를 다시 생각해보면 생성 -> 주입 이다. 즉, 빈 A와 빈 B를 먼저 만들고 각각 서로를 DI하는 데에는 문제가 없다. 반면 생성자는 DI와 생성을 원자적으로 한다. A를 만드려면 B가 있어야하고 B를 만드려면 A가 있어야 한다. 생성 시점에 오류가 나는 것이다.

     

     

    마치며

    정리하면서 술술 써내려 갔는데 누군가 설명해보라하면 할 수 있을까??

    댓글

Designed by Tistory.