본문 바로가기
개발/스프링

[Spring] Dependency Injection을 수행하는 세 가지 방법

by 카펀 2022. 7. 11.

회사에서 진행하는 개발 프로젝트에 참여하며, 로직 참고를 위해 기존의 소스 코드를 많이 들여다 보게 되었습니다.

Spring을 공부하고 나니, 예전에는 봐도 잘 모르겠던 내용들이 이제는 눈에 제법 잘 들어오는데요.

그러던 중 재미있는 점을 발견했습니다.

 

아래는 회사 코드의 일부 예시입니다 (실제 코드 내용은 아니며, 같은 맥락으로 재구성한 내용입니다).

@Controller
public class ManageHistoryController() {
	
    @Inject
    public ManageHistorySerice manageHistoryService;
    
    @Inject
    public AuthService authService;
    
    @Inject
    public SearchParam searchParam
    
    @RequestMapping(...)
    
}

이걸 보고 저는 딱 생각이 들었죠.

'어? 이건 전에 공부했던 의존 관계 주입 (dependency injection; 흔히 의존성 주입이라고도 합니다)이구나.

그런데 이렇게 해 놓은게 과연 좋은 방법인가?'

 

이런 생각이 들게 되어, 의존 관계 주입에 대한 글을 정리하여 작성하게 되었습니다.

이전에 이미 작성한 내용이 있긴 한데, 첫 글을 작성한 당시에는 단순히 이론적으로만 배운 내용이었다면, 이번에는 실제 프로젝트에서 경험한 바가 포함되어 조금 더 자세해진 글이라고 할 수 있겠습니다.

 

0. 개요

1. Setter 주입 방법

2. 생성자 주입 방법

3. 필드 주입 방법

4. 어떤 방법이 가장 적합한가?

5. 기타

 

0. 개요

먼저, 이 글을 읽기 전에 Spring에서 언급되는 의존 관계 주입 (Dependency Injection; 의존성 주입)에 대한 맥락이 있으셔야 합니다.

DI에 대해서는 다른 글에서 한 번 다룬 바 있습니다.

Spring에서 의존 관계 주입은 크게 세 가지 방법이 있습니다.

  • @Resources 사용
  • @Inject 사용
  • @Autowired 사용

이 중 흔히 사용되는 것은 @Inject와 @Autowired입니다. 둘은 거의 비슷한 기능을 제공하지만, 약간의 차이점이 있습니다. 

이 중 어떤 것을 사용해야 하는지는 저도 개인적으로 많이 궁금합니다.

 

의존 관계 주입 @Autowired vs @Inject - 인프런 | 질문 & 답변

안녕하세요! 스프링의 의존 관계 주입에 대해서 공부하다가 의문점이 생겨서 질문을 남깁니다. 스프링에서 의존 관계를 주입하는 어노테이션은 @Resources, @Inject, @Autowired 세 가지가 있는 것으로

www.inflearn.com

 

Difference between @Autowired and @Inject annotation in Spring?

A blog about Java, Programming, Algorithms, Data Structure, SQL, Linux, Database, Interview questions, and my personal experience.

javarevisited.blogspot.com

 

아무튼, Spring을 사용하다 보면 의존 관계 주입은 반드시 설정하게 됩니다.

이를 진행할 수 있는 각 방법들에 대해 소개합니다.

 

1. Setter 주입 방법

Setter 주입 방법은 아래와 같이 진행됩니다.

 

@RestController
public class PostsController {

    private PostsService postsService;

    @Autowired
    public void setPostsService(PostsService postsService) {
        this.postsService = postsService;
    }
    
    ...
    
}

 

Setter는 수정자라고도 합니다. 위와 같이 postsService는 private로 선언되고, 이를 설정해 주는 Setter 메소드가 public으로 열려 있습니다.

이 방법은 Setter가 public으로 열려 있게 되므로, Spring 시작 이후에 의존 관계의 변경이 이루어 질 수 있습니다.

 

2. 생성자 주입 방법

생성자 주입 방법은 아래와 같이 진행됩니다.

 

@RestController
public class PostsController {

    private final PostsService postsService;

    @Autowired
    public PostsController(PostsService postsService) {
        this.postsService = postsService;
    }
    
    ...
    
}

 

생성자를 통해 의존 관계가 주입됩니다. 이를 통해 얻을 수 있는 이득이 몇 가지 있습니다.

하나, 생성자의 호출 시점에 1회 실행되는 것이 보장됩니다.

둘, final 을 사용할 수 있습니다. 따라서 값이 반드시 설정되어야 하므로, 값이 설정되지 않는 오류를 컴파일 시점에 막을 수 있습니다.

셋. Spring Bean의 경우에만 해당하지만, 생성자가 하나 뿐이라면 @Autowired를 생략해도 동작합니다 (Spring 버전 4.3 이상일 경우에만 해당).

 

3. 필드 주입 방법

필드 주입 방법은 아래와 같이 진행됩니다.

 

@RestController
public class PostsController {

    @Autowired
    private PostsService postsService;
    
    ...
    
}

위의 방법들에 비해 코드가 많이 간결해져서, 과거에 널리 쓰이던 방법입니다.

이 방법은 Spring과 같은 DI 프레임워크가 반드시 필요합니다. 

 

4. 어떤 방법이 가장 적절한가?

우선, 필드 주입 방법을 먼저 살펴 보겠습니다.

IntlliJ를 사용 중이라면, 필드 주입 방식으로 코드를 작성 시 아래와 같은 메세지가 나타납니다.

'Field Injection is not recommended'. 즉, 필드 주입 방식은 추천되지 않는다는 뜻입니다. (이래서 Eclipse보단 IntelliJ를! ㅎㅎ)

 

우선, 필드 주입 방식의 경우에는 치명적인 문제점이 몇 가지 존재합니다.

  • 외부에서 변경이 불가능합니다. 따라서 코드를 통해 테스트 하기에 어렵습니다.
  • DI 프레임워크가 없으면 아무것도 할 수 없습니다.

따라서 실제 product 코드에서는 필드 주입 방식은 사용하면 안 됩니다. 간단한 테스트 코드 내에서나, @Configuration을 이용한 설정 파일 등 특수한 곳에서만 제한적으로 사용하도록 합니다.

 

그러면 이제 남는 방법은 Setter 주입과 생성자 주입입니다. 비교해 보면 장단점이 명확한데요.

먼저, Setter 주입은 위에서 언급한 바와 같이 의존 관계의 변경이 일어날 때 사용하는 방법입니다. Spring이 실행되고 나서, 도중에 의존 관계를 바꾸어 주어야 한다면, Setter를 통해 주입 받도록 해 주면 좋겠습니다.

하지만 의존 관계가 도중에 바뀔 일은 거의 없다고 볼 수 있습니다. 즉 굉장히 드문 경우이며, 오히려 이로 인해 얻는 손해가 훨씬 큽니다.

개발은 사람이 하는 것이고, 프로젝트가 커지다 보면 실수할 여지가 늘어나게 됩니다. 의존 관계 주입을 위한 Setter는 public으로 열려 있게 되는데, 이를 다른 사람이 보고 무심코 사용한다면?

아마 의도치 않은 버그가 일어날 것이고, 이를 찾기도 굉장히 어려울 것입니다.

따라서 애초에 변경할 수 없도록 막아두는 것이 설계상 좋습니다.

 

생성자 주입은 실행 시 최초 1회에 한해 실행됨이 보장됩니다.

final을 이용하여 불변하며, 필수로 주입받도록 설정할 수 있고, 이는 설계상 매우 좋은 접근 방법입니다.

이러한 장점 때문에 Spring에서도 생성자 주입 방식을 적극 추천하고 있으며, 앞서 언급한 바와 같이 생성자가 단 한 개만 존재하는 경우, Spring Bean에 한해 @Autowired 어노테이션을 생략할 수 있습니다.

 

따라서 결론은?

생성자 주입 방식을 사용합시다.

 

저 역시 이를 파악하고, 생성자 주입 방식을 적극 활용하기로 했습니다.

앞서 제가 회사에서 본 코드는 필드 주입 방식으로 되어 있었죠?

현실적인 여건 상 이미 만들어진 코드를 제가 손 댈 수는 없지만, 제가 개발한 Controller, Service 등에서는 모두 생성자 주입 방식으로 의존 관계 주입을 받도록 하였습니다.

SI 프로젝트 특성상 이렇게 한다고 해서 아무도 알아 주지는 않겠지만, 스스로 더 실력 있는 개발자로 성장하고자 한다면, 이런 작은 부분에서부터 더 좋은 방법으로 코드를 작성해야 한다고 생각합니다.

 

5. 기타

앞서 언급한 필드 주입 방식의 유일한 장점이 있었지요. "코드가 간결해진다."

개발자들은 코드를 한 줄이라도 덜 작성하고 같은 기능을 구현하는 것을 좋아합니다. 생성자 주입 방법 역시 예외는 아닌데요.

 

이를 위해 @lombok을 사용할 수 있습니다.

생성자 주입 방식을 사용하며 Lombok의 @RequiredArgsConstructor를 사용한다면, 아래와 같이 됩니다.

 

@RestController
@RequiredArgsConstructor
public class PostsController {

    private final PostsService postsService;
    
    ...
    
}

 

@RequiredArgsConstructor 어노테이션이 필요한 생성자를 자동으로 만들어 줍니다.

이렇게 하면 코드도 확실히 간결해지니, 굳이 필드 주입 방식을 사용할 필요가 없겠지요?

생성자 주입 방식을 사용합시다.

댓글