인프런 - 라이프타임 커리어 플랫폼
프로그래밍, 인공지능, 데이터, 마케팅, 디자인등 입문부터 실전까지 업계 최고 선배들에게 배울 수 있는 곳.
www.inflearn.com
인프런 사이트의 김영한님의 강의를 보면서 작성한 글입니다.
스프링으로 애플리케이션을 개발할 때, 우리는 Controller, Service, Repository와 같이 역할을 분리하여 계층형 구조로 설계합니다. 이때 MemberController가 MemberService를 필요로 하는 것처럼, 각 컴포넌트는 다른 컴포넌트를 의존하게 됩니다.
이러한 의존관계는 개발자가 직접 객체를 생성(new)하여 관리할 수도 있지만, 스프링을 사용하는 가장 큰 이유는 스프링 컨테이너에게 이 모든 것을 맡기기 위해서입니다.
이번 포스팅에서는 스프링 컨테이너가 객체를 어떻게 관리(스프링 빈)하고, 이 객체들 사이의 의존관계를 어떻게 연결(DI)해 주는지 알아보겠습니다.
## 1. 스프링 빈(Bean)과 스프링 컨테이너
MemberController 클래스에 @Controller 어노테이션을 붙이면, 스프링은 시작 시점에 이 클래스의 객체를 하나 생성해서 스프링 컨테이너라는 특별한 공간에 보관합니다. 이렇게 스프링 컨테이너가 관리하는 모든 객체를 우리는 **스프링 빈(Bean)**이라고 부릅니다.
만약 컨트롤러가 new MemberService()처럼 서비스를 직접 생성하면, 다른 컨트롤러가 MemberService를 필요로 할 때마다 새로운 객체를 계속 만들어야 합니다. 이는 메모리 낭비입니다. 따라서 MemberService 역시 스프링 컨테이너에 단 하나만(싱글톤) 등록해두고, 모두가 이를 공유해서 쓰는 것이 훨씬 효율적입니다.
## 2. 의존관계 주입(DI)과 @Autowired
MemberController가 스프링 컨테이너에 있는 MemberService 빈을 사용하려면, 둘 사이를 연결해달라는 요청이 필요합니다.
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
}
- 생성자 주입: 위와 같이 생성자의 파라미터로 MemberService를 받도록 선언합니다.
- @Autowired: 생성자에 이 어노테이션을 붙여주면, 스프링이 컨테이너에서 알아서 MemberService 타입의 빈을 찾아 주입해 줍니다. 이것이 바로 **의존관계 주입(Dependency Injection, DI)**입니다.
여기서 문제가 발생합니다. @Controller는 스프링 빈이지만, MemberService는 @Service 같은 어노테이션이 없는 순수 자바 클래스이므로 스프링 빈이 아닙니다. 따라서 스프링 컨테이너는 주입할 MemberService 빈을 찾지 못해 오류를 발생시킵니다.
이 문제를 해결하려면, 의존성의 대상이 되는 MemberService와 그가 의존하는 MemberRepository 역시 모두 스프링 빈으로 등록해야 합니다.
## 3. 스프링 빈을 등록하는 2가지 방법
### 방법 1: 컴포넌트 스캔 (Component Scan)
가장 간단한 방법은 각 클래스에 어노테이션을 붙여주는 것입니다. 스프링은 애플리케이션 시작 위치(HelloSpringApplication)를 기준으로 하위 패키지를 모두 스캔하며 다음 어노테이션이 붙은 클래스를 찾아 자동으로 빈으로 등록합니다.
- @Controller: 컨트롤러 계층
- @Service: 서비스 계층 (핵심 비즈니스 로직)
- @Repository: 리포지토리 계층 (데이터 접근)
이 어노테이션들은 모두 내부에 @Component 어노테이션을 포함하고 있어, 사실상 모두 같은 스캔 대상입니다.
### 방법 2: 자바 코드로 직접 등록 (Java Configuration)
컴포넌트 스캔 방식 대신, 설정 파일을 만들어 수동으로 빈을 등록할 수 있습니다. 이는 향후 구현체를 변경해야 할 때 매우 유용합니다.
service 폴더 등에 SpringConfig 클래스를 하나 만들고 다음과 같이 작성합니다.
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
- @Configuration: 이 파일이 스프링의 설정 파일임을 알립니다.
- @Bean: 이 메소드가 반환하는 객체를 스프링 빈으로 등록하라고 알립니다.
## 4. DI의 3가지 방식과 '생성자 주입'을 권장하는 이유
DI를 수행하는 방법에는 크게 3가지가 있습니다.
- 필드 주입 (Field Injection): 필드에 바로 @Autowired를 붙이는 방식입니다.
- 단점: 코드가 간결해 보이지만, 외부에서 변경이 불가능하여 테스트하기가 매우 어렵습니다.
-
Java
@Autowired private MemberService memberService;
- 단점: 코드가 간결해 보이지만, 외부에서 변경이 불가능하여 테스트하기가 매우 어렵습니다.
- Setter 주입 (Setter Injection): set 메소드에 @Autowired를 붙이는 방식입니다.
- 단점: public으로 열려있어, 애플리케이션 동작 중에 누군가 이 메소드를 호출하여 의존관계를 변경할 위험이 있습니다.
-
Java
@Autowired public void setMemberService(MemberService memberService) { this.memberService = memberService; }
- 단점: public으로 열려있어, 애플리케이션 동작 중에 누군가 이 메소드를 호출하여 의존관계를 변경할 위험이 있습니다.
- 생성자 주입 (Constructor Injection)
- 장점: 스프링이 가장 권장하는 방식입니다. 객체가 생성되는 시점에 단 한 번만 호출되며, 의존관계를 **final**로 선언하여 불변성(immutability)을 확보할 수 있습니다. 또한, 테스트 코드 작성 시 의존성을 누락하지 않고 주입하기 편리합니다.
-
Java
@Autowired public MemberController(MemberService memberService) { this.memberService = memberService; }
- 장점: 스프링이 가장 권장하는 방식입니다. 객체가 생성되는 시점에 단 한 번만 호출되며, 의존관계를 **final**로 선언하여 불변성(immutability)을 확보할 수 있습니다. 또한, 테스트 코드 작성 시 의존성을 누락하지 않고 주입하기 편리합니다.
## 결론: 어떤 방식을 선택해야 하는가?
현재 우리의 상황(MemberRepository가 인터페이스임)을 고려할 때, 두 번째 방법인 **'자바 코드로 직접 등록'**하는 것이 가장 이상적입니다.
- Controller, Service처럼 정형화된 컴포넌트는 컴포넌트 스캔을 사용해도 좋습니다.
- 하지만 Repository처럼, 지금은 MemoryMemberRepository를 쓰지만 나중에는 JdbcMemberRepository나 JpaMemberRepository 등으로 구현 클래스를 변경할 가능성이 있는 컴포넌트는, SpringConfig에서 수동으로 빈을 등록하는 것이 좋습니다.
그렇게 하면, 나중에 데이터베이스가 정해졌을 때 SpringConfig의 코드 단 한 줄만 수정하면(return new Memory... -> return new Jdbc...) 애플리케이션의 다른 코드 수정 없이 의존관계를 손쉽게 교체할 수 있습니다.
'개발 공부 > 백엔드' 카테고리의 다른 글
| 스프링 DB 접근 기술 : 1. H2 데이터베이스 연동과 순수 Jdbc (0) | 2025.09.27 |
|---|---|
| 스프링 부트 - 스프링 MVC로 회원 관리 웹 기능 만들기 (0) | 2025.09.25 |
| 스프링 부트 회원 관리 예제 만들기: 설계부터 테스트, DI까지 (2) | 2025.09.16 |
| 백엔드 공부 중 (1. ASP, 2. JSON) (0) | 2025.09.14 |
| 스프링 웹 개발 기초: 3가지 방식으로 웹 요청 처리하기 (0) | 2025.09.14 |