섹션 9 : 빈 생명주기 콜백
데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면 객체의 초기화와 종료 작업이 필요하다.
스프링 빈
객체 생성 -> 의존관계 주입
따라서 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출한다.
스프링 빈의 이벤트 라이프사이클
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료
초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
소멸 전 콜백 : 빈이 소멸되기 직전에 호출
스프링은 의존관계 주입이 완료되면 초기화 콜백을 실행하고, 컨테이너가 종료되기 전에는 소멸 콜백을 실행한다.
=> 안전하게 종료 작업을 진행할 수 있다.
객체의 생성과 초기화를 분리하는 것이 좋다.
생성자 → 필수 정보(파라미터)를 받고, 메모리를 할당하는 역할
초기화 메서드 → 생성된 값들을 활용해 외부 커넥션 연결 등 무거운 작업을 수행
객체 생성과 초기화를 분리하면 유지보수성이 향상된다.
즉, 생성자는 객체를 만드는 역할만 하고, 초기화 메서드는 필요한 동작을 수행하는 역할을 하도록 설계하는 것이 바람직하다.
스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.
- 인터페이스(InitializingBean, DisposableBean)
- 설정 정보에 초기화 메서드, 종료 메서드 지정
- @PostConstruct, @PreDestory 애노테이션 지원
인터페이스
package hello.core.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;
}
// 서비스 시작시 호출
public void connect(){
System.out.println("connect: " + url);
}
public void call(String message){
System.out.println("call: " + url + "message = " + message);
}
//서비스 종료시 호출
public void disconnet(){
System.out.println("close: " + url);
}
// 의존관계 주입이 끝나면 호출
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("NetworkClient.afterPropertiesSet");
connect();
call("초기화 연결 메시지");
}
// 빈이 종료될 때 호출
// 컨테이너가 내려가서 종료하기 전에 싱글톤 빈들이 하나씩 죽음
@Override
public void destroy() throws Exception {
System.out.println("NetworkClient.destroy");
disconnet();
}
}
package hello.core.lifecycle;
import org.junit.jupiter.api.Test;
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 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;
}
}
}
BeanLifeCycleTest 코드를 실행하면
위의 사진과 같은 결과가 나온다.
NetworkClient 객체가 생성될 때 생성자가 호출되며 "생성자 호출, url = null"이 출력된다.
이때 url이 아직 설정되지 않았기 때문에 null이 출력된다.
⬇️
스프링이 의존관계 주입을 완료한 뒤 afterPropertiesSet() 메서드를 호출하며 "NetworkClient.afterPropertiesSet"이 출력된다.
이 시점에서 setUrl("http://hello-spring.dev")이 실행되어 url 값이 설정된다.
⬇️
그다음 connect()가 실행되면서 "connect: http://hello-spring.dev"이 출력되고, call("초기화 연결 메시지")가 실행되어
"call: http://hello-spring.dev message = 초기화 연결 메시지"가 출력된다.
⬇️
마지막으로 스프링 컨테이너가 종료되면서 destroy()가 호출되고 "NetworkClient.destroy"가 출력된다.
이후 disconnect()가 실행되면서 "close: http://hello-spring.dev"이 출력된다.
초기화, 소멸 인터페이스 단점
- 이 인터페이스는 스프링 전용 인터페이스라, 해당 코드가 스프링 전용 인터페이스에 의존한다.
- 초기화, 소멸 메서드의 이름을 변경할 수 없다.
- 외부 라이브러리에 적용할 수 없다.
빈 등록 초기화, 소멸 메소드 지정
설정 정보에 @Bean(initMethod = "init", destroyMethod = "close")처럼 초기화, 소멸 메서드를 지정할 수 있다.
public void init() {
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메시지");
}
public void close() {
System.out.println("NetworkClient.close");
disconnet();
}
@Configuration
static class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
이렇게 함수를 직접 구현한 후, 초기화 소멸 메서드를 직접 지정하는 방식도 있다.
설정 정보를 사용하는 것의 장점
- 메서드 이름을 자유롭게 줄 수 있다.
- 스프링 빈이 스프링 코드에 의존하지 않는다.
- 코드가 아니라 설정 정보를 사용하기 때문에 외부 라이브러리에도 최고화, 종료 메서드를 적용할 수 있다.
종료 메서드의 추론
@Bean(initMethod = "init", destroyMethod = "close")
라이브러리는 대부분 close, shutdown 이라는 종료 메서드를 사용한다.
@Bean destroyMethod는 기본값이 (inffered)(추론)으로 등록되어 있다.
이 기능은 말 그대로 close, shutdown라는 이름의 종료 메서드를 추론해서 호출해준다.
따라서 직접 스프링 빈을 등록하면 종료 메서드를 따로 적어주지 않아도 잘 동작한다.
애노테이션 @PostConstruct, @PreDestory
@PostConstruct
public void init() {
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메시지");
}
@PreDestroy
public void close() {
System.out.println("NetworkClient.close");
disconnet();
}
@Configuration
static class LifeCycleConfig {
@Bean
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
최신 스프링에서 가장 권장하는 방법이다.
- 애노테이션 하나만 붙이면 되므로 매우 편리하다.
- 패키지를 잘 보면 `javax.annotation.PostConstruct` 이다. 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
-
- 외부 라이브러리에는 적용하지 못한다. 외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 기능(init 메서드를 사용해야한다.)
'Spring Study' 카테고리의 다른 글
[스프링 MVC] #1 (0) | 2025.04.10 |
---|---|
[스프링 핵심 원리] - 기본편 #9 (1) | 2025.04.10 |
[스프링 핵심 원리] - 기본편 #7 (0) | 2025.04.02 |
[스프링 핵심 원리] - 기본편 #6 (0) | 2025.04.02 |
[스프링 핵심 원리] - 기본편 #5 (1) | 2025.03.21 |