스프링 프레임워크를 통해 개발을 하다보면 아래와 같은 메시지를 만날 수 있습니다.
Field injection is not recommended
인텔리제이가 Field injection
방법은 추천하지 않는다고 친절하게 설명해줍니다.
클래스의 필드를 통한 DI 방법은 빈이 생성된 직후, 어떤 config 메서드도 호출되기 전에 수행이 된다라고 추가적으로 알려주고 있습니다.
왜 Field injection 이 추천되지 않는지에 들어가기전에 dependency injection에 대해 간단히 알고 들어가겠습니다.
DI는 객체가 필요로하는 (의존하는) 객체를 직접 생성하거나 참조하는 것이 아니라 외부로부터 주입받아 사용하는 방식입니다.
그래서 DI 원칙을 사용하게 되면 의존성이 주입되는 객체로 인해서 클래스간 결합도가 감소하게 됩니다.
또한 의존성을 주입받는 객체는 의존성의 위치나 클래스를 알지 못합니다.
특히 의존되는 객체의 타입이 인터페이스인 경우 테스트하기 쉬워지며 확장을 쉽게 할 수 있게 됩니다.
스프링 프레임워크에서는 IoC Container를 통해 DI를 지원하고 객체(빈)를 생성하고 관리하며 의존성을 자동으로 관리해줍니다.
스프링의 공식문서에는 의존성 주입 방법을 크게 두 가지로 설명해주고 있습니다.
Constructor-based dependency injection
Setter-based dependency injection
사실 이것 말고도 위에서 잠깐 보았던 Field-based dependency injection
이 존재합니다.
그러면 각각에 대해서 살펴보도록 하겠습니다.
@Controller
class ImageController {
private final ImageService imageService;
@Autowired
public ImageController(ImageService imageService) {
this.imageService = imageService;
}
}
생성자 주입 방식은 빈이 생성될 때 의존성이 주입됩니다.
그렇기 때문에 아래 두 방식과는 다르게 주입받는 필드를 final
로 선언함으로써 불변과 null
이 아님을 보장해줍니다.
스프링 4.3부터는 빈의 생성자가 하나만 존재할 경우 생성자위에 @Autowired
애노테이션을 생략해도 자동으로 빈이 주입됩니다.
@Controller
class ImageController {
private ImageService imageService;
@Autowired
public void setImageService(ImageService imageService) {
this.imageService = imageService;
}
}
Setter 기반의 DI는 빈이 생성자 기반 방식과는 다르게 빈이 생성될 때 의존성이 주입되지 않고 빈 생성이 완료되면 setter
를 호출해서 의존 빈을 주입해주게 됩니다.
그렇기 때문에 필수적인 의존성에 관해서는 null
을 확인해주어야 합니다.
하지만 setter 기반의 DI를 사용하면 해당 클래스의 객체를 나중에 구성하거나 다시 주입해줌으로써 의존성을 변경할 수 있다는 점이 있습니다.
@Controller
class ImageController {
@Autowired
private ImageService imageService;
}
필드 기반의 DI도 setter 기반의 DI와 마찬가지로 빈 생성이 완료된 이후에 의존성을 주입해주게 됩니다.
@Autowired
애노티에션도 필드에 달아주면 의존성이 알아서 주입되어 가독성이 좋아보입니다. 하지만 위에서도 봤듯이 이 방식은 추천되지 않습니다.
불변성
위에서도 봤듯이 생성자 기반의 DI만이 final
키워드를 사용할 수 있습니다.
Setter와 필드 방식은 주입되는 필드를 변경 가능한(mutable) 상태를 만들기 때문에 애플리케이션 실행 중 의존 관계가 바뀌게 되어 위험할 수 있습니다.
DI Container와의 결합
필드 기반 주입방식은 빈을 주입할 수 있는 생성자나 setter가 제공되지 않기 때문에 Reflection API를 사용하지 않는한 DI Container가 없으면 빈을 주입받을 수 없습니다. 그렇기 때문에 Spring Container와 강한 결합도를 가지게 됩니다.
순환 참조의 문제 인지불가
순환 참조는 A라는 빈이 생성될 때 B라는 빈을 필요로 하고, B라는 빈도 생성될 때 A라는 빈이 필요한 문제입니다. 이 경우 스프링은 빈을 초기화하지 못하고 BeanCurrentlyInCreationException
을 발생시킵니다. 하지만 필드 기반의 주입을 사용할 경우 메서드를 사용하는 것이 아닌 Reflection API
를 사용하여 직접 주입해주기 때문에 이런 문제를 알지 못합니다.
SRP를 지키기 어렵게 한다.
필드 기반의 DI는 클래스가 직접 의존하는 객체를 생성하고 관리하기 때문에, 해당 객체의 생성과 생명 주기를 관리하는 책임까지 클래스가 갖게 됩니다. 이는 해당 클래스가 수행하는 작업 이외의 책임을 지니게 되어 SRP를 위배할 가능성이 높아지게 됩니다.
반면에 생성자 주입 방식을 사용하면 객체를 생성하는 책임은 클래스가 아니라 스프링 DI 컨테이너에게 있습니다. 클래스는 필요한 객체를 매개변수로 받아 해당 객체를 사용하기만 하면 됩니다. 이렇게 되면 클래스는 자신이 수행해야 하는 작업에만 집중할 수 있으며, SRP를 지키기 쉬워집니다.
스프링 프레임워크를 사용할 때 빈 주입방식은 세 가지가 있습니다.
이중 필드 기반 빈 주입방식은 DI Container와 강한 결합, SRP 등의 문제로 사용하지 않는 것이 좋습니다.
필수적인 의존성의 경우 생성자 기반 빈 주입 방법을,
선택적인 의존성의 경우 setter 기반 빈 주입 방법을 선택하는 것이 좋아 보입니다.