의존성 주입할 때 왜 @Autowired
를 지양해야하는걸까. intelliJ에서는 다음과 같이 말하고 있다
Field injection is not recommended
Inspection info: Spring Team recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies".
Always 생성자 주입을 권장하고 있다. 각각의 장단점에 대해 정리하지 전에 먼저 의존성 주입 3가지 방법에 대해 알아보고 들어가자
CodeInputFileService.class
@Service
public class CodeInputFileService {
private final CodeInputFileManager codeInputFileManager;
public CodeInputFileService(CodeInputFileManager codeInputFileManager) {
this.codeInputFileManager = codeInputFileManager;
}
...
단일 생성자인 경우에는 @Autowired
어노테이션 조차 붙이지 않아도 되지만 생성자가 2개 이상인 경우에는 생성자에 어노테이션을 붙여주어야 한다.
CodeInputFileService.class
@Service
public class CodeInputFileService {
@Autowired
private CodeInputFileManager codeInputFileManager;
...
@Autowired
선언으로 필드에서 주입이 가능하다. 간편한 방법으로 많이 사용하지만 이번에는 왜 이를 지양해야하는지 알아본다
CodeInputFileService.class
@Service
public class CodeInputFileService {
private CodeInputFileManager codeInputFileManager;
@Autowired
public void setCodeInputFileManager(CodeInputFileManager codeInputFileManager) {
this.codeInputFileManager = codeInputFileManager;
}
...
Setter 주입은 생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우에 사용한다. 주로 사용하지는 않는 방법이다.
CodeInputFileService.class
@Service
public class codeInputFileService {
@Autowired
private BoardService boardService;
...
CodeInputFileService.class
@Service
public class BoardService {
@Autowired
private CodeInputFileService codeInputFileService;
...
위와 같이 서로 순환 참조하는 구조가 되었을 때, 필드 주입으로 구현했다면, 해당 참조가 실행돼 StackOverflowError가 발생하기 전까지는 알 수 없다. 하지만 생성자 주입으로 구현되어있다면 어플리케이션이 구동될 때 다음과 같이 에러가 나오는 것을 볼 수 있다.
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
mainController (field private com.wcp.board.BoardService com.wcp.board.main.MainController.boardService)
┌─────┐
| boardService defined in file [C:\git\2021\WebCompile\Toy-Project\core\out\production\classes\com\wcp\board\BoardService.class]
↑ ↓
| codeInputFileService defined in file [C:\git\2021\WebCompile\Toy-Project\core\out\production\classes\com\wcp\coding\inputFile\CodeInputFileService.class]
└─────┘
필드 주입과 수정자 주입은 해당 필드를 final로 선언할 수 없다. 수정자 주입이나 일반 메소드 주입을 이용하게 된다면 불필요하게 수정의 가능성을 열어두게 되는 것으로 차후 변경이 되었을 때 발생하는 Side-Effect를 사전에 감지할 수 없다
DI의 핵심은 관리되는 클래스가 DI 컨테이너에 의존성이 없어야 한다는 것이다. 즉, 독립적으로 인스턴스화가 가능한 POJO(Plain Old Java Ojbect) 여야 한다는 것이다. DI 컨테이너를 사용하지 않고서도 단위 테스트에서 인스턴스화할 수 있어야 한다. 생성자 주입을 하게 되면, Test Case 생성 시, 원하는 구현체를 넘겨주면 되고, 구현체를 넘겨주지 않은 경우에는 객체생성 자체가 불가능하기 때문에 테스트하기도 편하다.
CodeInputFileServiceTest.class
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
public class CodeInputFileServiceTest {
private final CodeInputFileService codeInputFileService;
public CodeInputFileServiceTest(CodeInputFileService codeInputFileService) {
this.codeInputFileService = codeInputFileService;
}
@Test
public void methodTest(){
...
편리함으로 사용해왔던 필드 주입은 지양하고 생성자 주입을 지향하는 것을 권한다.
참고