일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- nestjs
- lombok
- Google OAuth
- Spring
- 일급 컬렉션
- synchronized
- factory
- 일급 객체
- middleware
- Volatile
- builder
- spring security
- java
- Dependency Injection
- OAuth 2.0
- Today
- Total
HJW's IT Blog
스프링 빈 초기화·소멸 로직 : 인터페이스, Bean, @PostConstruct 본문
빈 생명주기 콜백
빈 생명주기 콜백 시작
어플개발을 하다 보면, 어플리케이션 시작 시점에 특정 서비스나 리소스를 준비하거나, 연결해둔 뒤에 어플리케이션 종료 시점에 이를 깔끔하게 정리하는 로직이 필요한 경우가 있다.
예를 들어
- DB Connection Pool 연결 : 어플리케이션 구동 시점에 미리 연결 풀을 구성해두면 빠른 DB 연동이 가능하다
- 외부 서비스와의 Socket 연결 : 초기에 연결을 맺어 두었다면, 어플리케이션 종료 전에 연결을 해제해야 정상적인 종료가 가능하다.
이와 같이 시작할때, 준비, 끝날때 정리 는 매우 흔한 요구사항이다. 그렇다면 우리는 스프링 빈이 언제 준비 되는지와, 어떤 시점에 초기화 / 정리 로직을 호출할 수 있는지를 명확하게 알아야
다음과 같은 예시를 보자
public class NetworkClient {
private String url;
public NetworkClient(){
System.out.println("생성자 호출, url = " + url);
connect();
call("초기화 연결 메시지");
}
public void setUrl(String url){
this.url = url;
}
public void connect(){
System.out.println("connect: " + url);
}
public void call(String message){
System.out.println("call: " + url + " message = " + message);
}
public void disconnect(){
System.out.println("close: " + url);
}
}
public class BeanLifeCycleTest {
@Test
public void lifeCycleTest(){
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
NetworkClient client = ac.getBean(NetworkClient.class);
ac.close();
}
@Configuration
static class LifeCycleConfig{
@Bean
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
}
결과는 다음과 같다.
생성자 호출, url = null
connect: null
call: null message = 초기화 연결 메시지
분명 setUrl 을 통해 url을 할당 해 주었지만, 결과를 보면 url 에 대한 정보가 없다. 이는, url 정보 없이 객체를 생성했기 때문이다.
스프링 빈은 다음과 같은 라이프 사이클을 가진다 : 객체 생성 → 의존관계 주입
스프링 빈은, 위 사이클을 거쳐야 데이터를 사용할 수 있는 준비가 완료되는 것이다. 즉, 초기화 작업은 의존관계 설정이 완료된 뒤에 호출해야 한다. 하지만 위 예제에서는, 객체 생성 시점에 이미 connect() 와 call() 을 호출했기 때문에, url 이 세팅되지 않은 상황에서 초기화 로직이 실행되어 버렸다. 즉, 의존관계 주입이 끝난 뒤에 초기화를 해야 하는데, 그 순서가 지켜지지 않은 것이다. 그렇다면 어떻게 개발자가 의존관계 주입이 모두 완료된 시점을 알 수 있을까?
→ 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점을 알려주는 다양한 기능을 제공한다. 이에 추가로 스프링은 스프링 컨테이너가 종료되기 직전 소멸 콜백을 준다.
다음은 스프링 빈의 event lifecycle 이다
- Spring Container 생성 → Spring Bean 생성 → Dependency Injection → 초기화 콜백 → 사용 → 소멸 전 콜백 → 스프링 종료
이제 Spring 이 빈 생명주기 콜백을 하는 3가지 방법을 알아보자.
인터페이스 : InitializingBean, DisposableBean
위에서 작성한 NetworkConnection 클래스를 다음과 같이 리팩토링 해보자.
package hello.core.lifecycle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class NetworkClient implements InitializingBean, DisposableBean {
//.. 기존 코드
@Override
public void afterPropertiesSet() throws Exception{
connect();
call("초기화 연결 메시지");
}
@Override
public void destroy() throws Exception {
disconnect();
}
}
결과는 다음과 같다
생성자 호출, url = null
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
close: http://hello-spring.dev
InitializingBean
InitializingBean 은 빈이 초기화될 때 실행해야 할 로직을 정의하게 되는데, afterPropertiesSet() 메서드를 통해 Spring 컨테이너가 빈의 의존성을 주입하고, 모든 초기화 작업이 완료된 뒤 호출된다.
이 메서드는 Bean 이 완전히 생성된 후 실행된다
DisposableBean
DisposableBean 은 빈이 소멸될 때 실행되어야 하는 로직을 정의한다. 스프링 컨테이너가 Application Context 를 종료하거나 빈을 제거할 때 호출된다. 소멸 로직이 여기에 작성된다.
초기화, 소멸 인터페이스의 단점
- 스프링 전용 인터페이스이기에, 해당 코드가 스프링 전용 인터페이스에 의존함다
- 초기화, 소멸 메서드의 이름을 변경할 수 없다.

지금은 더 나은 방법들이 있으니 참고만 하자.
빈 등록 초기화, 소멸 메서드 지정
스프링에선, 설정 정보에 @Bean(initMethod = "init", destroyMethod = "close") 처럼 초기화, 소멸 메서드를 지정할 수 있다. 이를 적용해서 위 코드를 리팩토링 해보자.
package hello.core.lifecycle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class NetworkClient {
private String url;
public NetworkClient(){
System.out.println("생성자 호출, url = " + url);
}
public void setUrl(String url){
this.url = url;
}
public void connect(){
System.out.println("connect: " + url);
}
public void call(String message){
System.out.println("call: " + url + " message = " + message);
}
public void disconnect(){
System.out.println("close: " + url);
}
public void init(){
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메시지");
}
public void close(){
System.out.println("NetworkClient.close");
disconnect();
}
}
위와 같이 정의해준 뒤, 설정 정보에 초기화 소멸 메서드를 지정하면 된다 .
@Configuration
static class LifeCycleConfig{
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
결과
생성자 호출, url = null
NetworkClient.init
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
NetworkClient.close
close: http://hello-spring.dev
설정 정보 사용 특징
- 메서드 이름을 자유롭게 지정할 수 있다
- 스프링 빈이 스프링 코드에 의존하지 않는다
- 코드가 아니라 설정 정보를 사용하기 때문에 코드를 코칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.

@Bean 의 destroyMethod 는 close, shutdown 이름의 종료 메서드를 기본값으로 추론하도록 되어 있다.
에노테이션 : @PostConstruct, @PreDestroy
package hello.core.lifecycle;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class NetworkClient {
private String url;
public NetworkClient(){
System.out.println("생성자 호출, url = " + url);
}
public void setUrl(String url){
this.url = url;
}
public void connect(){
System.out.println("connect: " + url);
}
public void call(String message){
System.out.println("call: " + url + " message = " + message);
}
public void disconnect(){
System.out.println("close: " + url);
}
@PostConstruct
public void init() {
System.out.println("NetClient.init");
connect();
call("초기화 메시지");
}
@PreDestroy
public void close() {
System.out.println("NetClient.close");
disconnect();
}
}
생성자 호출, url = null
NetClient.init
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 메시지
NetClient.close
close: http://hello-spring.dev
어노테이션 특징
- 최신 스프링에서 가장 권장하는 방법으로, 매우 편리하다
- 이 어노테이션은 스프링에 종속적인 기술이 아닌 JAVA 표준 기술이다.
- ComponentScan 과 어울린다.
- 유일한 단점은 외부라이브러리에는 적용이 안된다는 점이다.
각 방법 비교
방법
|
스프링 종속성
|
메서드 명 변경
|
외부 라이브러리 적용
|
사용 권장도
|
InitializingBean/DisposableBean
|
있음
|
불가능
|
어려움
|
낮음
|
@Bean(initMethod/destroyMethod)
|
없음
|
가능
|
가능
|
보통
|
@PostConstruct / @PreDestroy
|
없음
|
해당 없음(표준)
|
직접 적용 어려움(설정으로 가능)
|
매우 높음
|
마무리 및 정리
이번 글에서는 스프링 빈의 생명주기 콜백에 대해 알아보았다. 예제를 통해 우리는 “언제 빈이 완성되는가?” 와 “언제 리소스를 정리해야 하는가?” 라는 질문을 통해 어플리케이션 생명 주기에 대해 한층 더 이해할 수 있었다. 실제로 애플리케이션 실무 운영 환경에서는 시작 시점의 준비와 종료 시점의 정리가 매우 중요하다.
- 처음에는 스프링 고유 인터페이스(InitializingBean, DisposableBean)를 사용할 수 있지만, 이는 스프링 종속적이다
- 더 유연한 방식으로는 @Bean(initMethod, destroyMethod)를 통해 메서드를 지정할 수 있다
- 가장 간결하고 권장되는 방법은 바로 @PostConstruct, @PreDestroy 애노테이션을 사용하는 것이다.
'SPRING' 카테고리의 다른 글
JPA SQL - 가독성 좋게 보기 (0) | 2025.02.19 |
---|---|
프론트 컨트롤러와 DispatcherServlet (0) | 2025.01.09 |
컴포넌트 스캔과 의존성 자동 주입 (0) | 2024.12.04 |
스프링 싱글톤 컨테이너의 동작 원리 (0) | 2024.12.02 |
스프링 컨테이너와 스프링 빈 (1) | 2024.11.29 |