들어가며
Spring을 배우면서 가장 먼저 마주치는 개념 중 하나가 의존성 주입(DI, Dependency Injection) 입니다.
"의존성을 주입한다"는 말이 처음엔 어렵게 느껴질 수 있습니다.
이 글에서는 의존성 주입이 무엇인지, Spring 컨테이너가 어떻게 Bean을 만들고 연결해주는지 정리해보겠습니다.
의존성(Dependency)이란?
의존성이란 한 클래스가 다른 클래스를 필요로 하는 관계입니다.
public class MemberService {
private MemberRepository memberRepository; // MemberService는 MemberRepository에 의존
}
MemberService가 동작하려면 MemberRepository가 반드시 필요합니다.
이때 MemberService는 MemberRepository에 의존한다고 합니다.
의존성 주입이 없다면?
의존성 주입 없이 개발자가 직접 객체를 생성하는 경우입니다.
public class MemberService {
// 개발자가 직접 생성 - 의존성 주입 아님
private MemberRepository memberRepository = new MemberRepository();
}
이 방식의 문제점은 아래와 같습니다.
문제 1. 강한 결합 - MemberService가 MemberRepository를 직접 생성
→ MemberRepository가 바뀌면 MemberService도 수정해야 함
문제 2. 테스트 어려움 - 가짜 객체(Mock)로 교체 불가
문제 3. 객체 관리 부담 - 개발자가 모든 객체의 생성과 소멸을 직접 관리
의존성 주입이란?
의존성 주입은 객체가 필요로 하는 다른 객체를 직접 생성하지 않고, 외부(Spring 컨테이너)에서 만들어서 연결해주는 것입니다.
// 의존성 주입 - 직접 생성하지 않고 외부에서 받음
@Service
public class MemberService {
private final MemberRepository memberRepository;
// Spring이 MemberRepository를 만들어서 여기에 주입
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
MemberService는 MemberRepository를 직접 만들지 않고, Spring 컨테이너가 만들어서 전달해줍니다.
Spring 컨테이너란?
Spring 컨테이너는 Bean을 생성하고, 관리하고, 필요한 곳에 주입해주는 공간입니다.
개발자 대신 객체의 생성과 소멸, 의존성 연결을 모두 처리합니다.
[Spring 컨테이너]
┌────────────────────────────────────┐
│ │
│ MemberController ──▶ MemberService│
│ ↑ │
│ MemberService ──▶ MemberRepository│
│ ↑ │
│ MemberRepository (DB 접근) │
│ │
│ (객체 생성 및 의존성 연결 자동 처리) │
└────────────────────────────────────┘
@Component 계열 어노테이션 - Bean 등록 신호
Spring 컨테이너는 아래 어노테이션이 붙은 클래스를 자동으로 찾아 Bean으로 등록합니다.
| 어노테이션 | 역할 | 계층 |
|---|---|---|
@Component |
일반 Bean 등록 | 공통 |
@Controller |
웹 요청 처리 | Presentation |
@Service |
비즈니스 로직 | Business |
@Repository |
DB 접근 처리 | Data |
@Controller, @Service, @Repository는 내부적으로 모두 @Component를 포함하고 있습니다.
// @Service 어노테이션 내부 구조
@Component // @Component를 포함
@Target(ElementType.TYPE)
public @interface Service { ... }
컴포넌트 스캔 - Bean 자동 등록 과정
Spring Boot 애플리케이션이 시작되면 컴포넌트 스캔(Component Scan) 이 동작합니다.
지정된 패키지를 탐색하며 어노테이션이 붙은 클래스를 자동으로 Bean으로 등록합니다.
[애플리케이션 시작 과정]
@SpringBootApplication 실행
↓
컴포넌트 스캔 시작 (패키지 탐색)
↓
@Component, @Service, @Repository, @Controller 클래스 탐색
↓
발견된 클래스를 Bean으로 생성
→ MemberRepository Bean 생성
→ MemberService Bean 생성 (MemberRepository 주입)
→ MemberController Bean 생성 (MemberService 주입)
↓
의존성 자동 연결 완료
↓
애플리케이션 실행
코드로 전체 흐름 이해하기
// 1. Repository - DB 접근
@Repository
public class MemberRepository {
public Member findById(Long id) {
// DB 조회 로직
return new Member(id, "홍길동", 25);
}
}
// 2. Service - 비즈니스 로직
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository; // Spring이 주입
public Member getMember(Long id) {
return memberRepository.findById(id);
}
}
// 3. Controller - 웹 요청 처리
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/members")
public class MemberController {
private final MemberService memberService; // Spring이 주입
@GetMapping("/{id}")
public Member getMember(@PathVariable Long id) {
return memberService.getMember(id);
}
}
[요청 흐름]
클라이언트 GET /api/members/1
↓
MemberController.getMember(1)
↓ (Spring이 주입한 MemberService 사용)
MemberService.getMember(1)
↓ (Spring이 주입한 MemberRepository 사용)
MemberRepository.findById(1)
↓
{ "id": 1, "name": "홍길동", "age": 25 } 응답
의존성 주입 전후 비교
// 주입 전 - 개발자가 직접 생성, 강한 결합
public class MemberController {
private MemberService memberService = new MemberService(
new MemberRepository() // 연쇄적으로 직접 생성
);
}
// 주입 후 - Spring이 자동 연결, 느슨한 결합
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService; // Spring이 알아서 주입
}
| 항목 | 직접 생성 | 의존성 주입 |
|---|---|---|
| 객체 생성 | 개발자가 직접 new |
Spring 컨테이너가 대신 생성 |
| 결합도 | 강한 결합 | 느슨한 결합 |
| 테스트 | Mock 교체 어려움 | Mock 교체 용이 |
| 유지보수 | 변경 시 영향 범위 큼 | 변경 시 영향 범위 작음 |
| 객체 관리 | 개발자가 직접 관리 | Spring이 생명주기 관리 |
정리
- 의존성 주입은 객체가 필요로 하는 다른 객체를 직접 생성하지 않고, Spring 컨테이너가 만들어서 자동으로 연결해주는 것이다.
@Component,@Service,@Repository,@Controller어노테이션이 붙은 클래스는 Spring 컨테이너에 Bean으로 자동 등록된다.- 애플리케이션 시작 시 컴포넌트 스캔으로 Bean을 탐색하고, 의존성을 자동으로 연결한다.
- 의존성 주입을 사용하면 강한 결합이 느슨한 결합으로 바뀌어 유지보수성과 테스트 용이성이 높아진다.
@RequiredArgsConstructor를 함께 사용하면 생성자 코드 없이 간결하게 의존성 주입을 구현할 수 있다.