Spring 의존성 주입은 무조건 생성자 주입방식으로 작성해야 한다? 스프링을 사용하면서 가장 많이 접하는 코드는 의존성 주입에 관련된 @Autowired 어노테이션일 겁니다. 스프링 컨네이너에서 컴포넌트 스캔을 통해 빈으로 등록된 객체의 인스턴스를 @Autowired 어노테이션을 통해서 사용하고자 하는 객체에 주입시켜 줍니다. 객체의 생성과 객체간의 의존관계를 스프링 컨테이너가 해주는 거죠. 서로 의존관계에 있는 객체가 상대객체의 생성에 직접 관여하지 않고 컨테이너가 대신 함으로써 이전까지는 객체의 생성과 소멸 등의 생명주기를 그 객체를 사용하는 클래스에서 관리를 했다면, 이제는 스프링 컨테이너가 모두 일임하게 되었습니다. 우리는 컨테이너가 그 객체들을 스캔할 수 있도록 @Autowired만 붙여주면 됩니다. 물론 그 객체는 스캔이 가능한 컴포넌트로 등록되어 있는 빈 객채여야 합니다. 스프링을 사용하는 가장 큰 이유(이점) 중에 하나입니다. Spring에서 지원하는 의존성 주입방식은 3가지입니다.
필드 주입 방식
수정자(Setter) 메서드 주입방식
생성자 메서드 주입방식
참고로 Spring4.3 버전 이후부터는 수정자와 생성자 주입방식에서 @Autowired 어노테이션을 생략할 수 있습니다. 아마 많은 분들이 별 다른 고민 없이 필드 인젝션 방식으로 의존성 주입을 사용했을 텐데, 그리고 인텔리J와 같은 IDE툴에서도 필드 인젝션을 사용한 코드에 대해서 경고 메시지를 뿌려주기 시작했습니다. 그럼 왜 필드 인젝션은 사용하지 말아야 할 방식이 된 걸까요? 확인해야 할 포인트
한 가지 명확하게 짚고 넘어가야 할 것은, 필드 인젝션을 ‘’권장하지 않는다’’이지 ‘사용하면 안 된다’가 아닙니다. 필드 인젝션의 문제점이라고 많이 얘기하는 내용 중에 대표적인 두 가지 문제점입니다.
첫 번째, 주입받는 빈이 NPE를 일으킬 가능성이 있는 얼마나 될까요? 스프링 컨테이너가 컴포넌트 스캔을 통해 @Autowired가 붙어있는 빈객체의 주입시켜 줄텐데, 사용할 클래스에서 빈을 임의로 Null로 변경하지 않는 이상, 사실 주입받은 빈 객체가 NPE를 일으킬 가능성은 없습니다. 실제로 코딩을 하면서 필드 인젝션 방식으로 인해 주입받은 인스턴스를 참조할 때 초기화 이슈 등으로 NPE를 경험한 적은 한 번도 없었습니다. 주입받을 클래스는 스프링 컨테이너에서 빈 객체로 등록될 수 있는 상태여야 하기 때문에 애플리케이션이 구동될 때 빈으로 등록되는 과정에서 초기화가 제대로 이루어지지 않으면 빈 등록 자체가 되지 않습니다. 두 번째, 주입받은 빈의 변경이 가능한 문제는 필드 인젝션 만의 문제가 아니라, 수정자/생성자 인젝션 방식도 동일한 문제를 일으킵니다. 너무 당연한 얘기이죠. 어떤 방식이 되었든 사용하고자 하는 클래스에서 주입받은 객체는 임의로 변경을 할 수 있습니다. 단, final로 선언된 빈의 경우에는 변경이 불가능합니다. 생성자 인젝션인 권장되는 이유 중에 하나가 필드 인젝션 또는 수정자 인젝션의 경우 final로 의존성 주입이 불가능하고, 생성자 인젝션을 통해서만 가능하기 때문입니다. 빈 객체의 ‘immutable’ 보장할 수 있는 유일한 주입방식인 거죠. 생성자 주입 방식을 사용해야 하는 이유확실한 것은, 생성자 방식으로 객체의 의존성을 관리하게 되면, 의존성 객체가 추가되거나 삭제될 때 필드 인젝션보다는 코딩해야 하는 부분이 많아집니다. 생성자 메서드의 매개변수를 추가 또는 삭제해줘야 하며, 생성자 메서드 내의 소스도 수정이 필요합니다. 코드 한 줄 추가하거나 삭제하면 되는 필드 인젝션 방식과 비교하면 분명 번거로운 일입니다. 어떨 때는 매개변수에만 추가하고 빈에 할당하는 코드를 빼먹는 경우도 있습니다. 물론 Lombok라이브러리를 사용하면 간단히 처리할 수 있습니다. 아래에서 간단히 언급하였습니다. 이 부분이 제 개인적인 생각으로는 의존성 관리(추가, 삭제)의 번거로운 부분이 역설적으로 생성자 주입방식을 사용해야 하는 이유 중 하나라고 생각합니다. 생성자 방식을 사용해야 하는 이유, 아니 사용을 권장하는 이유는 사실 이런 이유이지 않을까 생각합니다. 2번은 위에서 언급했던 의존성 주입 작업의 번거로움이 그 이유라고 언급했던 내용과 일맥상통하는 부분이 있습니다.
1번의 경우, 주입받은 객체를 임의로 변경하는 경우는 실제 서비스 구현 시 거의 일어나지 않는 케이스일 것 같습니다. 그렇게 해서도 안 됩니다. 그런 리스크를 원천적으로 차단할 수 있는 방법입니다. 2번의 경우, 주입받는 객체가 1,2개일 때는 그냥 필드 인젝션이나 수정자 인젝션을 사용해도 상관없다고 생각합니다. 의존관계가 1:1, 1:2 정도라는 것은 객체 간의 의존관계가 잘 설계되었다는 의미이고, 굳이 기존 코드들을 생성자 주입방식으로 변경할 필요가 없다고 봅니다. 클래스의 설계 원칙인 단일 책임원칙(SRP: Single-Responsibility Principle)을 위배할 정도로 의존성 관계가 많아진다면, 필드 인젝션의 경우 그 의존관계가 잘 드러나 지지 않기 때문에 생성자 주입방식을 사용함으로써 그 관계를 잘 드러낼 수 있습니다. 생성자 메서드의 매개변수를 보면 얼마나 많은 의존성 객체들을 주입받았는지 확인할 수 있습니다.
아이러니한 것은, 생성자 주입방식을 쓸 때 코드 작성을 최소화하기 위해서 Lombok의 @RequiredArgConstructor 어노테이션을 많은 분들이 사용할 텐데, 이렇게 되면 방금 언급한 의존 성관계를 잘 드러낼 수 있는 생성자 주입방식의 장점이 사라져 버립니다. lombok 어노테이션이 생성자 메서드를 컴파일 시 만들어 주기 때문에 코드에서는 드러나지 않게 되는 거죠. 정리하면, 장점 정도의 수준으로 그 사용이 더 바람직하다는 접근이 필요하지 않을까 하는 개인적인 생각입니다. 더불어 테스트 코드 작성 시, 특정 DI 프레임워크에 의존하지 않고 POJO기반으로 작성이 가능하다는 장점도
있겠네요. 생성자 인젝션 방식이 필드인젝션 방식보다 무조건 더 좋다는 접근은 피해야 하지 않을까 생각합니다. 미뤄뒀던 코드 리팩터링을 하면서, |