여기서부터는 Spring에서 가장 중요한 개념 중 하나인 Bean 생명주기(Lifecycle) 를 다룬다.
많은 개발자가
new UserService();
정도만 생각하지만,
Spring에서는 객체가 만들어지고 사라질 때까지 여러 단계의 생명주기를 거친다.
그리고 이 과정을 이해해야
- @PostConstruct
- @PreDestroy
- BeanPostProcessor
- @Transactional
- @Async
- AOP
가 언제 동작하는지 자연스럽게 이해할 수 있다.
객체 생명주기와 Spring Bean
Spring Bean은 생성만 되는 것이 아니라 평생 관리된다.
이전 글에서는
@ComponentScan
↓
BeanDefinition 생성
↓
Reflection 객체 생성
↓
DI
↓
BeanPostProcessor
↓
Singleton Cache 저장
까지 살펴봤다.
그런데 여기서 하나의 질문이 생긴다.
Spring은 Bean을 언제 생성하고, 언제 사용할 수 있으며, 언제 제거할까?
이 질문의 답이 바로 Bean Lifecycle(생명주기) 이다.
일반 Java 객체의 생명주기
우리가 직접 객체를 생성하면 매우 단순하다.
UserService service =
new UserService();
service.save();
흐름은
new
↓
생성자 실행
↓
사용
↓
참조 제거
↓
GC
이다.
객체가 언제 사라질지는
GC가 결정한다.
개발자가 제어할 수 없다.
Spring Bean은 다르다.
Spring은
객체를 단순히 생성하는 것이 아니라
생명주기 전체를 관리한다.
Bean 생성
↓
DI
↓
초기화
↓
사용
↓
소멸
이 모든 과정이 Spring Container 안에서 이루어진다.
전체 생명주기
가장 먼저 전체 그림을 보자.
Application Start
│
▼
Reflection으로 객체 생성
│
▼
생성자(Constructor)
│
▼
Dependency Injection
│
▼
BeanPostProcessor(before)
│
▼
@PostConstruct
│
▼
InitializingBean.afterPropertiesSet()
│
▼
Custom initMethod
│
▼
BeanPostProcessor(after)
│
▼
사용 가능(Ready)
==============================
Application Shutdown
│
▼
@PreDestroy
│
▼
DisposableBean.destroy()
│
▼
Custom destroyMethod
1단계
생성자(Constructor)
Spring은 Reflection으로 객체를 생성한다.
@Service
public class UserService {
public UserService() {
System.out.println("Constructor");
}
}
실행
Constructor
이 시점에는
아직 DI가 끝나지 않았다.
즉,
@Service
public class OrderService {
@Autowired
private UserService userService;
public OrderService() {
userService.save();
}
}
여기서는
userService == null
일 수 있다.
생성자에서
다른 Bean에 의존하는 로직은 작성하지 않는 것이 좋다.
2단계
Dependency Injection
객체가 만들어지면
Spring은 필요한 Bean을 주입한다.
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
이 시점부터
userService
↓
사용 가능
이다.
3단계
BeanPostProcessor(before)
Spring 내부에는
BeanPostProcessor라는 확장 포인트가 있다.
Bean이 생성될 때마다
자동으로 실행된다.
Bean 생성
↓
postProcessBeforeInitialization()
Spring 내부의 수많은 기능이
여기서 시작된다.
4단계
@PostConstruct
가장 많이 사용하는 초기화 방식이다.
@Service
public class UserService {
@PostConstruct
public void init() {
System.out.println("Init");
}
}
실행 순서
Constructor
↓
DI
↓
@PostConstruct
즉,
이 시점에는
모든 의존성이 이미 주입되어 있다.
그래서
@PostConstruct
public void init() {
userService.load();
}
처럼
다른 Bean을 안전하게 사용할 수 있다.
왜 생성자가 아니라 @PostConstruct를 사용할까?
생성자
객체만 생성
↓
DI 아직 안 됨
@PostConstruct
DI 완료
↓
초기화 수행
초기 데이터 로딩,
캐시 생성,
외부 연결 준비 등에 적합하다.
5단계
InitializingBean
Spring이 제공하는 인터페이스이다.
public class UserService
implements InitializingBean {
@Override
public void afterPropertiesSet() {
System.out.println("Init");
}
}
@PostConstruct와 거의 같은 역할이다.
하지만
Spring 인터페이스에 의존하게 되므로
최근에는
@PostConstruct
를 더 많이 사용한다.
6단계
initMethod
Configuration에서도 초기화할 수 있다.
@Bean(initMethod = "init")
public UserService userService() {
return new UserService();
}
public void init() {
}
가 자동으로 호출된다.
7단계
BeanPostProcessor(after)
초기화가 끝나면
다시 BeanPostProcessor가 실행된다.
postProcessAfterInitialization()
이 단계에서
가장 중요한 일이 발생한다.
@Transactional
@Service
public class UserService {
@Transactional
public void save() {
}
}
Spring은
UserService
↓
Transaction Proxy
↓
Bean Container 저장
으로 변경한다.
즉,
실제로 Container에 저장되는 것은
원본 객체가 아니라
Proxy일 수도 있다.
사용 단계
이제 Bean은
Ready 상태이다.
@Autowired
private UserService userService;
Controller
↓
Service
↓
Repository
모든 곳에서
동일한 Singleton Bean을 사용한다.
종료 단계
Application 종료
↓
Bean 소멸
↓
@PreDestroy 실행
@PreDestroy
public void close() {
System.out.println("Close");
}
대표적인 사용 예
Connection 종료
ThreadPool 종료
파일 저장
캐시 정리
DisposableBean
implements DisposableBean
@Override
public void destroy() {
}
역시
Spring 인터페이스에 의존하기 때문에
최근에는
@PreDestroy
가 더 많이 사용된다.
Scope에 따라 생명주기가 달라진다.
Singleton
Application Start
↓
생성
↓
계속 사용
↓
Application 종료
↓
소멸
Prototype
요청
↓
생성
↓
사용
↓
Spring 관리 종료
Spring은
생성까지만 하고
소멸은 관리하지 않는다.
Request Scope
HTTP 요청
↓
Bean 생성
↓
Controller
↓
Service
↓
응답 완료
↓
Bean 소멸
웹 환경에서만 사용된다.
BeanPostProcessor가 중요한 이유
많은 Spring 기능은
여기서 구현된다.
Bean 생성
↓
BeanPostProcessor
↓
Proxy 생성
↓
Container 저장
대표적으로
- @Transactional
- @Async
- @Cacheable
- AOP
가 모두 이 과정을 거친다.
그림으로 전체 정리
Reflection 객체 생성
│
▼
Constructor
│
▼
Dependency Injection
│
▼
BeanPostProcessor(before)
│
▼
@PostConstruct
│
▼
InitializingBean
│
▼
initMethod
│
▼
BeanPostProcessor(after)
│
▼
Proxy 생성 가능
│
▼
Ready
==============================
Application 종료
│
▼
@PreDestroy
│
▼
DisposableBean
│
▼
destroyMethod
실무에서는 이렇게 기억하면 된다.
초기화
생성자
↓
DI
↓
@PostConstruct
↓
사용 시작
종료
Application 종료
↓
@PreDestroy
↓
자원 정리
면접 포인트
Spring Bean 생명주기는?
생성
↓
DI
↓
초기화
↓
사용
↓
소멸
생성자에서 다른 Bean을 사용하면 안 되는 이유는?
DI가 아직 완료되지 않았기 때문이다.
@PostConstruct는 언제 실행되는가?
모든 의존성이 주입(DI 완료)된 이후, Bean이 사용되기 직전에 실행된다.
BeanPostProcessor는 무엇인가?
Bean 생성 후 전후에 개입할 수 있는 확장 포인트이며, Spring의 Proxy 기반 기능(@Transactional, @Async, AOP 등)이 이 메커니즘을 이용한다.
다음 글
다음은 Dynamic Proxy와 Spring AOP이다.
이 글에서는 지금까지 배운
Reflection
↓
Bean 생성
↓
BeanPostProcessor
↓
Proxy 생성
이 실제로 AOP가 되는 과정을 코드와 그림으로 연결해서 설명하면 Spring 내부 구조가 하나의 흐름으로 완성된다.
'language > java' 카테고리의 다른 글
| JDK Proxy vs CGLIB in Spring (0) | 2026.06.17 |
|---|---|
| Dynamic Proxy와 Spring AOP (0) | 2026.06.17 |
| Bean 생성 과정 (0) | 2026.06.17 |
| Reflection과 Spring DI (0) | 2026.06.17 |
| Virtual Thread(Java 21) (0) | 2026.06.17 |
댓글