이전까지는 이런 구조로 왔다.
- Controller(서블릿)가 요청을 받고
- Service/DAO를 호출하고
- JSP로 forward/redirect로 흐름을 제어한다
문제는 규모가 커질수록 코드가 이렇게 변한다는 점이다.
- 컨트롤러가 서비스 객체를 직접 new로 만들기 시작한다
- 서비스가 DAO를 직접 new로 만들기 시작한다
- 구현체가 바뀌면 여기저기 코드가 같이 바뀐다
- 테스트가 어려워진다(가짜 객체로 바꾸기 힘들다)
이 문제를 해결하려면 “객체를 누가 만들고, 누가 누구를 참조하게 만들지”를 분리해야 한다.
이때 등장하는 핵심 개념이 DI(의존성 주입)이다.
// ================================
// [DI가 없을 때] Controller가 Service를 직접 new 해서 결합이 강해진다
// - 구현체 교체/테스트가 어려워진다
// ================================
public class TodoController {
private final TodoService service = new TodoService(); // ❌ 직접 생성(강한 결합)
public void handle() {
service.register("study");
}
}
DI는 “의존 객체를 외부에서 주입받는 방식”이다
DI(Dependency Injection)는 말 그대로 의존성을 주입하는 것이다.
여기서 의존성은 이런 의미이다.
- A가 일을 하려면 B가 필요하다
- 즉 A는 B에 의존한다
전통적인 방식(강한 결합)은 A가 B를 직접 만든다.
A 내부에서 new B()
DI 방식(느슨한 결합)은 A가 “B가 필요하다”만 말하고,
B를 누가 만들지는 외부가 결정한다.
A 생성자에 B를 넣어준다
핵심은 이거다.
객체 생성과 객체 사용을 분리한다
빈(Bean)은 “스프링이 관리하는 객체”이다
스프링은 DI를 하기 위해 “객체를 대신 생성하고 관리”한다.
스프링이 관리하는 객체를 빈(Bean)이라고 부른다.
즉 빈은 단순히 “객체”가 아니라
- 스프링 컨테이너가 생성하고
- 생명주기를 관리하고
- 필요한 곳에 주입하는
관리 대상 객체이다.
ApplicationContext는 “빈을 담아두는 컨테이너”이다
스프링에는 빈들을 담아두는 큰 상자가 있다.
그게 ApplicationContext이다.
ApplicationContext는 이런 역할을 한다.
- 어떤 빈이 등록되어 있는지 알고 있다
- 빈을 생성한다
- 의존 관계를 연결해서 주입한다
- 같은 빈을 재사용(싱글톤 기본)해서 관리한다
즉 “내가 new 하지 않아도 되는 이유”가 여기에 있다.
스프링을 쓰면 개발자는 “객체 생성”이 아니라 “등록과 주입”을 한다
스프링을 쓰기 시작하면 개발자의 작업 방식이 바뀐다.
- 예전: 필요한 객체를 직접 new 해서 연결한다
- 스프링: 빈으로 등록하고, 필요한 곳에서는 주입받는다
그래서 코드의 초점이
- “어디서 new 했지?”가 아니라
- “이 객체는 빈으로 등록됐나?”로 바뀐다
생성자 주입이 기본이 되는 이유
DI는 여러 방식이 있지만, 실무에서 가장 추천되는 건 생성자 주입이다.
생성자 주입이 좋은 이유는 단순하다.
- 의존성이 없으면 객체 생성이 애초에 불가능해져서 문제를 빨리 발견한다
- 필드를 final로 둘 수 있어서 안정적이다
- 테스트에서 가짜 객체를 쉽게 주입할 수 있다
그래서 스프링 3 이후로는 생성자 주입이 사실상 기본 패턴이 되었다.
// ================================
// [스프링 빈 등록 + 생성자 주입 예시]
// - @Service, @Controller 같은 어노테이션으로 빈 등록
// - @RequiredArgsConstructor로 생성자 주입을 간단히
// ================================
@Service
public class TodoService {
public void register(String title) { /* ... */ }
}
@Controller
@RequiredArgsConstructor
public class TodoController {
private final TodoService todoService; // 스프링이 생성자에 주입
@PostMapping("/todo/register")
public String register(String title) {
todoService.register(title);
return "redirect:/todo/list";
}
}
component-scan: “어노테이션 붙은 클래스를 찾아서 빈으로 등록한다”
스프링이 빈을 등록하는 방법은 크게 두 가지가 있다.
- XML에 <bean>으로 등록
- 컴포넌트 스캔(component-scan)으로 자동 등록
컴포넌트 스캔은 이런 동작이다.
지정한 패키지를 훑어보면서
@Component 계열 어노테이션이 붙은 클래스를 찾아
빈으로 등록한다
여기서 대표적인 어노테이션이 다음이다.
- @Controller
- @Service
- @Repository
- @Component
즉 “어노테이션을 붙이면 스프링이 빈으로 등록한다”는 말의 실체가 component-scan이다.
핵심 정리
- DI는 객체 생성과 사용을 분리해서 결합도를 낮추는 방식이다
- 스프링은 빈(Bean)이라는 관리 객체를 만들고 DI를 수행한다
- ApplicationContext가 빈을 생성/관리/주입하는 컨테이너이다
- 생성자 주입이 기본 패턴이고, @Controller/@Service 등은 component-scan으로 빈 등록된다
'Web > Web Basics' 카테고리의 다른 글
| [스프링과 스프링 MVC] 3. 파라미터 바인딩과 Model (0) | 2026.03.30 |
|---|---|
| [스프링과 스프링 MVC] 2. 스프링 MVC는 “프론트 컨트롤러”로 움직인다 (0) | 2026.03.30 |
| [상태 유지와 공통 처리] 5. MyBatis 설정 흐름 (0) | 2026.03.30 |
| [상태 유지와 공통 처리] 4. MyBatis는 무엇을 해결하는가 (0) | 2026.03.30 |
| [상태 유지와 공통 처리] 3. Connection Pool / DataSource / HikariCP (0) | 2026.03.30 |