데이터베이스 커넥션 풀이나 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화 종료 작업이 필요하다.
이번 시간에는 스프링을 통해 이러한 초기화 작업과 종료 작업이 어떻게 진행되는지 알아보겟다.
간단하게 외부 네트워크와 미리 연결할 객체를 하나 만들어보자.
이 객체는 애플리케이션시작 시점에 connect()를 호출해서 연결을 맺어두고, 애플리케이션이 종료되면 disConnect를 호출해서 연결을 끊어야 한다.
package hello.core_review.lifecycle;
public class NetworkClient {
private String url;
public NetworkClient() {
System.out.println("생성자 호출, url = " + url);
connect();
call("초기화 연결 메시지");
}
public void setUrl(String url) {
this.url = url;
}
private void connect() {
System.out.println("conect: " + url);
}
private void call(String message) {
System.out.println("call: " + url + " message = " + message);
}
//서비스 종료시 호출
public void disconnect() {
System.out.println("close: " + url);
}
}
이를 실행해보면
package hello.core_review.lifecycle;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class BeanLifeCycle {
@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("https:/hello-spring.dev");
return networkClient;
}
}
}

이런 결과가 나온다.
우리가 상상했던 결과가 아니다.
스프링빈은 객체 생성 후 의존 관계가 주입되는 라이프 사이클을 가지고 있다.
즉 객체를 생성하고, 의존 관계 주입이 끝나고 나서야 필요한 데이터를 사용할 수 있는 준비가 완료되는 것이다.
그렇기에 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출돼야 한다.
그런데 개발자가 어떻게 의존관계 주입이 모두 완료된 시점을 알 수 있을까?
스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공해준다!
또한 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다.
스프링 덕분에 안전하게 종료 작업을 진행할 수 있게 된다.
다시 정리하면 스프링 빈의 라이프 사이클은
스프링 컨테이너 생성 --> 스프링 빈 생성 --> 의존 관계 주입 --> 초기화 콜백 --> 사용 --> 소멸전 콜백 --> 스프링 종료
이다.
초기화 콜백은 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출하는 것을 의미한다.
소멸전 콜백은 빈이 소멸되기 직전에 호출하는 것을 의미한다.
이렇게 스프링은 다양한 방식으로 생명주기 콜백을 지원해준다.
*참고
- 객체를 생성하는 것과 초기화하는 부분을 분리하는 것이 좋다.
객체를 생성하는 것, 즉 생성자는 필수 정보인 파라미터를 받고 메모리를 할당받아 객체를 생성하는 기능을 수행한다.
초기화는 이렇게 생성된 객체를 활용해서 외부 커넥션와 연결하는 무거운 동작을 수행한다.
따라서 생성자 안에 초기화 작업을 함께 작성하는 것 보다는 이 둘을 분리하는 것이 유지 보수 관점에서 더 좋다.
위에서 작성한 NetworkClient 클래스는 생성자와 초기화가 함께 작성되어 있다.
- 싱글톤 빈들은 스프링 컨테이너가 종료될 대 싱글톤 빈들도 함께 종료되기 때문에 스프링 컨테이너가 종료되기 직전, 소멸전 콜백이 일어난다.
스프링은 크게 3가지 방법으로 빈 생명 주기 콜백을 지원한다.
- 인터페이스(InitalizingBean, DisposableBean)
- 설정 정보에 초기화 메서드, 종료 메서드 지정
- @PostConstruct, @PreDestroy 애노테이션 지원
인터페이스(InitalizingBean, DisposableBean)
package hello.core_review.lifecycle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class NetworkClient implements InitializingBean, DisposableBean {
private String url;
public NetworkClient() {
System.out.println("생성자 호출, url = " + url);
}
public void setUrl(String url) {
this.url = url;
}
//서비스 시작시 호출
private void connect() {
System.out.println("connect: " + url);
}
private void call(String message) {
System.out.println("call: " + url + " message = " + message);
}
//서비스 종료시 호출
public void disconnect() {
System.out.println("close: " + url);
}
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("초기화 연결 메시지");
}
@Override
public void destroy() throws Exception {
disconnect();
}
}
NetworkClient 클래스에 InitizlizingBean과 DisposableBean을 implement 하면 된다.
각각 afterPropertieSet()과 destroy() 메서드를 오버라이딩 하도록 되어 있다.
이렇게 하니

원했던 출력 결과가 나옴을 알 수 있다.
즉 초기화 메서드가 주입 완료 후에 적절하게 호출 되었으며, 스프링 컨테이너의 종료가 호출되자 메서드가 호출 되었다.
하지만 이 방법은 스프링 전용 인터페이스를 사용하기 때문에 해당 코드가 스프링 전용 인터페이스를 의존하게 된다.
또한 초기화, 소멸 메서드의 이름을 변경할 수 없고 내가 고칠 수 없는 외부 라이브러리에 적용할 수 없다는 단점을 갖는다.
이 방법은 스프링 초창기에 나온 방법이기에 요즘은 잘 사용하지 않는다.
빈 등록 초기화, 소멸 메서드 지정
설정 정보에 @Bean(inintMethod = "init", destroyMethod = "close")처럼 초기화, 소멸 메서드를 지정해주는 방법이다.
package hello.core_review.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;
}
//서비스 시작시 호출
private void connect() {
System.out.println("connect: " + url);
}
private 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();
}
}
우선 NetworkClient 클래스에 init 메서드와 close 메서드를 추가해준다.
그리고
@Configuration
static class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("https:/hello-spring.dev");
return networkClient;
}
}
이렇게 설정 정보 @bean에 초기화 메서드와 소멸 메서드를 지정해준다

이 방법은
메서드 이름을 자유롭게 줄 수 있다
스프링 빈이 스프링 코드에 의존하지 않는다
코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.
이렇게 인터페이스 방식보다 더 편리하게 사용 가능하다.
종료 메서드 추론
@Bean의 destroyMethod 속성에는 특별한 기능이 있다.
라이브러리는 대부분 close, shutdown이라는 이름의 종료 메서드를 사용한다. 그런데 @Bean의 destroyMethod는 기본 값이 (inferred) -> 추론으로 등록되어 있다.
이 추론 기능은 close, shutdown이라는 이름의 메서드를 자동을 호출해준다. 종료 메서드를 추론해서 호출해줄 수 있다는 것이다.
따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 작성해주지 않아도 잘 작동하는 것을 확인할 수 있다.
애노테이션 @PostConstruct, @PreDestroy
@PostConstruct
public void init() {
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메시지");
}
@PreDestroy
public void close() {
System.out.println("NetworkClient.close");
disconnect();
}
NetworkClient의 init 메서드와 close메서드 위에 애노테이션을 붙여주었다.
그리고 설정 정보 @Bean의 속성을 지워주었다.

동일한 결과를 도출하였다.
@PostConstruct와 @PreDestroy 이 두 애노테이션을 사용하면 가장 편리하게 초기화와 종료를 실행할 수 있다.
이 방법은
최신 스프링에서 가장 권장하는 방법이다.
애노테이션 하나만 붙이면 되기에 더 편리하고, 자바 표준 라이브러리에 등록된 기술이기에 스프링이 아닌 다른 컨테이너에서도 동작한다.
메서드 위에 애노테이션을 붙여주기만 하면 되기에 컴포넌트 스캔과 잘 어울린다.
하지만 외부 라이브러리에는 적용하지 못한다는 단점을 가지고 있다.
'Spring' 카테고리의 다른 글
| [Spring MVC] 스프링 MVC 패턴 정리 (0) | 2025.03.16 |
|---|---|
| [Spring 핵심 원리 이해] 8. 빈 스코프 (4) | 2025.01.13 |
| [Spring 핵심 원리 이해] 6. 의존관계 자동 주입 (6) | 2025.01.03 |
| [Spring 핵심 원리 이해] 5. 컴포넌트 스캔 (3) | 2024.12.28 |
| [Spring 핵심 원리 이해] 4. 싱글톤 컨테이너 (4) | 2024.12.27 |