| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- 문제선택근거
- 클린아키텍처
- Ajax
- 공통기술
- AOP
- 전전긍긍
- 공통규약
- Transaction
- 설정세팅
- index
- 냄새라도
- 이벤트핸들러this
- 커뮤니티서버
- 이벤트핸들러등록
- TDD
- string
- 로그백
- 디스크i/o
- springsecurity
- model
- 사면초가
- 단위테스트
- MVC요청플로우
- SpringLegacy
- OOP
- JDBC
- DDD
- JPA
- SEQUENCE
- MVC
- Today
- Total
개발이군고구마
JVM 클래스 로딩 vs Spring 컴포넌트 스캔: 같은 ‘로딩’이 아니다 본문
Spring을 사용하다 보면 아래와 같은 질문을 자주 갖게 된다.
- “이 클래스는 언제 로딩되었길래 사용할 수 있는(또는 없는) 걸까?”,
- “컴포넌트 스캔을 하면 바로 객체가 생성되는 걸까?”
- "컴포넌트 스캔의 결과 생성된 객체는 어떤 형태로 어디에 저장되는 걸까?"
나 역시 컴포넌트 스캔과 JVM의 클래스 로딩을 같은 흐름으로 이해하고 있었고,
이로 인해 클래스가 메모리에 올라가는 시점과 Spring이 빈을 생성하는 시점을 명확히 구분하지 못하고 있었다.
이 글에서는 다음 세 가지를 중심으로 정리한다.
- JVM의 클래스 로딩과 Spring의 컴포넌트 스캔은 어떤 점에서 다른가
- Spring 컨테이너는 언제 빈을 정의하고 언제 생성하는가
- JVM에 로딩된 클래스와 Spring 빈은 무엇이 다른가
1. JVM의 클래스 로딩과 Spring 컴포넌트 스캔은 별개의 과정이다
먼저 가장 중요한 결론부터 말하면
✅ JVM의 클래스 로딩과 Spring의 컴포넌트 스캔은 서로 전혀 다른 책임을 가진 과정이다.
1️⃣ JVM의 클래스 로딩
- JVM은 클래스 파일(.class)을 필요한 시점에 메모리에 로딩한다.
이 과정에서 클래스의 구조 정보(필드, 메서드, 상수 풀 등)가 Method Area에 저장된다. - 이 과정은 순수하게 JVM 관점의 동작이며,
Spring을 사용하든 사용하지 않든 동일하게 동작한다.
즉, 어떤 클래스가 JVM에 로딩되었다는 사실만으로는
그 클래스가 Spring에서 관리되는 객체라는 의미는 아니다
2️⃣ Spring의 컴포넌트 스캔
@Service
public class UserService {
@Autowired
private UserRepository repository;
}
- 반면 Spring의 컴포넌트 스캔은 IoC 컨테이너의 초기화 과정에서 수행된다.
- Spring은 설정된 패키지를 탐색하며
@Component, @Service, @Repository 같은 애노테이션이 붙은 클래스를 찾아낸다. - 그리고 이 시점에 객체를 생성하지는 않는다.
- 대신 Spring은 해당 클래스를 기반으로
“이런 빈이 필요하다”는 BeanDefinition(빈 정의) 을 먼저 등록한다. (Method area에 클래스 정보 등록)
컴포넌트 스캔의 결과는 객체가 아니라 ‘설계도(BeanDefinition)’ 다.
2. Spring 컨테이너는 언제 빈을 정의하고, 언제 생성할까?
이 부분이 가장 혼동되기 쉬운 지점이다. Spring의 빈 처리 과정은 크게 두 단계로 나뉜다.
1️⃣ 빈 정의(BeanDefinition) 등록 단계
ApplicationContext가 초기화되면서 다음 작업들이 순서대로 수행된다.
- 컴포넌트 스캔 실행
- @Component, @Service 등이 붙은 클래스 발견
- 각 클래스에 대한 BeanDefinition 생성 및 등록
- @Configuration 클래스 내부의 @Bean 메서드들도 BeanDefinition으로 등록
▶ 이 단계까지는 실제 객체는 하나도 생성되지 않는다.
Spring은 단지 “이 컨테이너에는 이런 빈들이 필요하다” 라는 메타정보만 수집한 상태다.
2️⃣ 빈 생성(Instantiation) 단계
그 다음 단계에서 Spring은 다음과 같은 순서로 동작한다.
- BeanFactoryPostProcessor 실행
→ BeanDefinition을 수정하거나 확장 - 의존관계 분석
- 싱글톤 빈 인스턴스 생성
- 의존성 주입
- 초기화 메서드 실행
- 컨테이너에 빈 등록 완료
▶ 이때 비로소 실제 객체가 Heap 메모리에 생성된다.
컴포넌트 스캔 시점 ≠ 빈 생성 시점
컴포넌트 스캔은 “정의”, 빈 생성은 “실체화” 단계다.
3️⃣ 코드로 알아보기
// 1. Component Scan이 이 클래스를 찾음
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@PostConstruct
public void init() {
System.out.println("UserService 초기화!");
}
}
// Phase 4 - BeanDefinition 등록
BeanDefinition bd = new RootBeanDefinition(UserService.class);
bd.setScope("singleton");
beanFactory.registerBeanDefinition("userService", bd);
// Phase 5 - 빈 인스턴스 생성
// Step 10-3-A: 클래스 로딩
Class<?> beanClass = Class.forName("com.example.UserService");
// Step 10-3-B: 인스턴스 생성 ⭐⭐⭐
Constructor<?> ctor = beanClass.getDeclaredConstructor();
Object instance = ctor.newInstance(); // ← new UserService() 실행되는 시점!
// Step 10-3-C: 의존성 주입
Field field = beanClass.getDeclaredField("userRepository");
field.setAccessible(true);
field.set(instance, userRepositoryBean); // userRepository 주입
// Step 10-3-D: 초기화 콜백
Method initMethod = beanClass.getMethod("init");
initMethod.invoke(instance); // @PostConstruct 실행
// Step 10-4: 싱글톤 캐시에 저장
singletonObjects.put("userService", instance);
3. JVM에 로딩된 클래스와 Spring 빈의 차이
| JVM에 로딩된 클래스 | Spring Bean | |
| 관리 주체 | JVM | Spring IoC 컨테이너 |
| 저장 내용 | 클래스 메타정보 | 실제 객체 인스턴스 |
| 메모리 영역 | Method Area | Heap |
| 생성 시점 | 필요할 때 | 컨테이너 초기화 과정 |
| 역할 | 코드 실행을 위한 준비 | 애플리케이션 객체 관리 |
- JVM은 클래스를 메타정보로 메모리에 올림
- Spring은 해당 클래스가 IoC 컨테이너에서 필요하다면 인스턴스로 생성해 빈으로 관리
JVM에 클래스가 로딩되었다고 해서
Spring이 그 클래스를 빈으로 관리한다는 보장은 없다.
▶ Spring 빈은 JVM 객체 중에서도 IoC 컨테이너가 생명주기를 관리하는 객체다.
4. 전체 흐름 정리 (타임라인)
Spring 초기화 타임라인을 통해 “정의(Definition) → 생성(Instantiation)” 을 명확하게 구분
[JVM 시작]
↓
[Environment 준비]
↓
[BeanFactory 초기화]
↓
[Context 생성]
↓
[컴포넌트 스캔 수행]
- 클래스패스를 스캔하여 .class 파일들을 찾음
- BeanDefinition 등록
- (아직 인스턴스 X)
↓
[BeanDefinition 등록 📝] ← Method Area: Spring 관련 클래스들, 스캔된 빈 클래스들 로드됨
↓
[BeanFactoryPostProcessor 실행]
↓
[싱글톤 빈 생성 시작 🎯]
├─ 의존성 분석
├─ 의존성부터 재귀적 생성
├─ 클래스 로딩 (필요시)
├─ ⭐빈 인스턴스 생성 🎯 new UserService() ← 여기!
├─ 의존성 주입 💉 ← @Autowired
├─ 초기화 콜백 🔧 ← @PostConstruct
└─ 싱글톤 캐시 저장
↓
[모든 빈 생성 완료]
↓
[ApplicationContext 준비 완료 ✅]
5. 정리하며
- JVM 클래스 로딩과 Spring 컴포넌트 스캔은 책임과 목적이 다르다
- 컴포넌트 스캔은 빈 정의를 등록하는 과정이지 객체 생성이 아니다
- Spring 빈은 JVM 객체 중에서도 컨테이너가 생명주기를 관리하는 특별한 객체다
이 차이를 명확히 이해해야 Spring의 초기화 과정이나 의존성 주입을 직관적으로 이용하고 활용할 수 있게 된다.
6. 참고 - spring framework 작동 순서와 application context 구조
[Tomcat 시작]
↓
[web.xml 또는 WebApplicationInitializer 읽기]
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 2: Root WebApplicationContext 생성
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
[ContextLoaderListener.contextInitialized()]
↓
[Root WebApplicationContext 생성 🎯]
↓
[RootConfig 로드 및 Component Scan]
↓
[Service, Repository 빈 생성]
- new UserService() ⭐
- new UserRepository() ⭐
↓
[Root Context → ServletContext에 저장]
↓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Phase 3: DispatcherServlet 초기화
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↓
[DispatcherServlet 인스턴스 생성]
- new DispatcherServlet() ⭐
↓
[DispatcherServlet.init() 호출]
↓
[Servlet WebApplicationContext 생성 🎯]
- parent = Root Context
↓
[WebConfig 로드 및 Component Scan]
↓
[Controller 빈 생성]
- new UserController() ⭐
- UserService 주입 (Root Context에서)
↓
[MVC 인프라 빈 생성]
- RequestMappingHandlerMapping
- RequestMappingHandlerAdapter
- ViewResolver
↓
[HandlerMapping 초기화]
- @RequestMapping 정보 수집
- URL → Controller 메서드 매핑 등록
↓
[다른 전략들 초기화]
↓
[DispatcherServlet 준비 완료 ✅]
↓
[요청 처리 가능 상태]
전체 컨텍스트 계층 구조
┌─────────────────────────────────────────────┐
│ ServletContext (Tomcat) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Root WebApplicationContext │ │
│ │ (ContextLoaderListener가 생성) │ │
│ │ │ │
│ │ 빈들: │ │
│ │ - dataSource │ │
│ │ - userRepository │ │
│ │ - userService │ │
│ │ - transactionManager │ │
│ └────────────────────────────────────┘ │
│ ↑ parent │
│ ┌────────────────────────────────────┐ │
│ │ Servlet WebApplicationContext │ │
│ │ (DispatcherServlet이 생성) │ │
│ │ │ │
│ │ 빈들: │ │
│ │ - userController ──→ userService │ │
│ │ - productController │ │
│ │ - handlerMapping │ │
│ │ - handlerAdapter │ │
│ │ - viewResolver │ │
│ └────────────────────────────────────┘ │
│ ↓ owns │
│ ┌────────────────────────────────────┐ │
│ │ DispatcherServlet 인스턴스 │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
'SERVER > Spring' 카테고리의 다른 글
| JDBC 트랜잭션의 원리 (0) | 2026.05.17 |
|---|---|
| @NoArgsConstructor & @AllArgsConstructor: 무작위 생성자 어노테이션의 진짜 의미 (0) | 2026.01.06 |
| [HandlerMethodArgumentResolver] 레거시코드, 고쳐도 될까? (0) | 2025.12.31 |
| [Spring Batch] 수기 업무의 늪에서 탈출하기: 조건부 흐름 제어와 대용량 처리 (1) | 2025.12.02 |
| [Spring Security] redis/DB로 자동로그인 구현 (0) | 2025.11.28 |