인텔리제이(IntelliJ) 사용 시에 “Field injection is not recommended” 메시지에 고통받은 경험이 있나요?

@RestController
@RequestMapping(value = "/api/ou")
@Api(value = "Organization Unit")
public class OrganizationController {

@Autowired
private OrganizationService organizationService;

.. // 생략
Code language: PHP (php)

그동안 의존관계를 주입할 때, 큰 고민 없이 관례처럼 @Autowired를 사용하여 필드주입 방식을 사용해 왔습니다. 이클립스(Eclipse)에서 @Autowired를 사용할 때, 마우스를 오버하면 이런저런 설명을 제공하기는 하지만 별 다른 경고나 예외 메시지를 보여주지 않기 때문입니다. (IDE 설정에 따라 다를 수 있음)

사실 @Autowired를 사용하는 방법은 여러 단점이 많아서 권장되고 있지는 않습니다. 너무 쉬운 의존성 주입, 그로 인한 의존성 책임의 문제, 순환 의존성, final 선언 불가로 인해 객체가 변할 수 있는 등… 심지어 테스트도 용이하지 않습니다. 그런데도 많은 사람들이 편하기 때문에, 또는 내 선배들이 그렇게 써왔기 때문에 너무 쉽게 따라서 사용하고 있을 것입니다.

사실 이클립스를 쓰면서는 이런 방식에 의문을 품었던 적이 맹세코 단 한 번도 없었다고 말할 수 있습니다. 그런데 인텔리제이로 툴을 변경한 뒤 어느 날 너무나 갑작스럽게 @Autowired에 흐린 회색의 밑줄이 그어져 있는 것을 보게 되었는데, 실제로 코드가 동작하는 데에는 큰 문제가 없었지만 조금씩 거슬리기 시작했습니다. SpringBoot로 Rest API를 주로 개발하는 업무 특성상, 거의 모든 클래스에서 의존성 관계를 추가하게 되고 모든 클래스에 경고성 메시지를 알리는 회색 밑줄이 표시된다는 뜻이기 때문입니다.

어느 날 시작된 거슬림은 점점 강렬해져 더는 무시하고 지나칠 수 없는 지경에 이르렀습니다. 혹시라도, 이미 자각했지만 귀찮아서 찾아보기를 미루었거나 Best Practice에 대해 조금은 고민을 하고 있을지도 모르는 친구들을 위해 짤막한 글을 작성해보기로 했습니다.

그렇다면 어떤 방법이 있을까요? Setter를 사용하거나, 생성자로 주입하는 방식을 고려할 수 있을 것입니다.

@RestController
@RequestMapping(value = "/api/hist")
@Api(value = "Organization Tree History")
public class HistoryController {

private HistoryService historyService;

@Autowired
public void setHistoryService (HistoryService historyService) {
this.historyService = historyService;
}

.. // 생략
Code language: PHP (php)

또는

@RestController
@RequestMapping(value = "/api/hist")
@Api(value = "Organization Tree History")
public class HistoryController {

private HistoryService historyService;

public HistoryController (HistoryService historyService) {
this.historyService = historyService;
}

.. // 생략
Code language: PHP (php)

와 같이 말입니다.

Setter를 사용하는 방식은 Spring 3.x 버전까지는 권장되었습니다. 하지만 현재 대세는 생성자 사용이라고 하는데요. 생성자를 사용하면 스프링에서 정의하는 어노테이션을 사용하지 않아도 되기 때문에 컨테이너와 독립적으로 클래스 작성과 테스트가 가능하기 때문이라고 합니다. 또한 생성자에서 주입되는 빈(Bean)들을 선언해야 하므로 해당 클래스에서 떠맡고 있는 책임을 명확하게 보여주면서 의존관계와 복잡성을 쉽게 파악할 수 있습니다.

장점이 이렇게 명확하기 때문에 생성자 주입 방식을 사용하는 게 훨씬 나아 보이는데, 더 개선할 여지가 있을까요? 여기에 최근 프로젝트에서 그 편리함 때문에 사랑받고 있는 Lombok을 뿌려보기로 합니다.

@RestController
@RequestMapping(value = "/api/hist")
@Api(value = "Organization Tree History")
@AllArgsConstructor
public class HistoryController {

private HistoryService historyService;

.. // 생략
Code language: PHP (php)

여기에 더 해서 만약 Immutability 이슈까지 해결하고 싶다면 다음과 같은 코드를 작성할 수 있겠죠.

@RestController
@RequestMapping(value = "/api/hist")
@Api(value = "Organization Tree History")
@RequiredArgsConstructor
public class HistoryController {

private final HistoryService historyService;

.. // 생략
Code language: PHP (php)

historyService를 final로 선언하면서 Immutability 이슈는 물론, Lombok을 사용함으로 final로 선언된 모든 속성에 대한 생성자를 생성하게 됩니다. 이제 종속성이 필요하지 않은 경우 생성자 매개 변수에 신경 쓸 것 없이 해당 필드만 삭제하면 되기 때문에 한층 간결하고 관리하기 쉬운 코드가 작성되었습니다. 다만 생성자 주입 방식에서 장점으로 보았던 명확한 책임 작성 관점에서는 어긋날 수 있기 때문에 장단점을 잘 파악해서 선택하면 더 좋을 것입니다.

읽어주셔서 감사하고, 오류나 개선 점이 있다면 남겨주세요. 피드백을 환영합니다.

참고 : Best Practices for Dependency Injection with Spring