스프링 빈의 생명주기란?
빈의 생명주기(라이프사이클)이란 스프링 컨테이너가 빈을 생성하고 관리하는 과정 전반을 의미한다.
빈 정의 → 빈 생성 → 의존관계 주입 → 빈 초기화(초기화 메소드 콜백) → 빈 사용 → 빈 소멸(소멸 전 콜백) 으로 이루어져 있다.
Q. 콜백 (콜백 메소드)란?
A. 콜백과 콜백 메소드는 같은 말이다. 객체가 특정 상태에 도달했거나 특정 이벤트가 발생했을 때 자동으로 호출되는 메소드이다. (초기화 상태 도달 → 초기화 메소드 콜백, 소멸 상태로 전환 → 종료 메소드 콜백)
Q. 빈 초기화와 초기화 메소드란?
A. 초기화 메소드는 빈이 생성된 후에 추가 설정과 초기화 작업(외부 서비스와의 연동, 속성 설정, 캐시 초기화 등)을 하기 위해 호출되는 메소드이다.
스프링 컨테이너를 통해 생성된 빈은 초기화를 거쳐 비로소 정상적으로 동작할 수 있는 상태가 된다.
- 스프링에서 객체의 생성과 초기화를 구분하면 좋은 이유 : 생성자는 필수 정보(매개변수)를 받고 객체를 생성하는 일을 해야 한다. 초기화 작업에서는 생성자가 만든 객체들을 활용해서 외부 커넥션을 연결하는 등 무거운 동작을 수행한다. 생성자 안에서 무거운 초기화 작업을 함께 하는 것보다는, 객체를 생성하는 부분과 초기화하는 부분을 명 확하게 나누는 것이 유지보수 관점에서 좋다.
Q. 스프링 빈은 왜 소멸되어야 하는가?
A. 스프링 컨테이너는 빈이 더이상 필요하지 않을 때 자동으로 빈을 소멸시킨다. 해당 빈에 대해 가비지 컬렉션이 발생하여 메모리에서 자동으로 해제된다. 사용이 완료된 빈이 소멸되어야 하는 이유는 1) 메모리 누수로 인한 시스템 성능 저하 방지, 2) 빈이 사용하던 리소스(파일 핸들러, 연결된 DB/Network)들의 해제 시점을 명시해주어야 함, 3) 빈의 소멸시점에 맞추어 캐시 데이터 업데이트 시점이 생겨남. …등이 있다.
이제 스프링 빈의 생명주기 안에서 콜백하는 방법을 알아보자.
(0) 예제 적용 전 기본 코드 (접은글)
package hello.core.lifecycle;
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);
}
}
package hello.core.lifecycle;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
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(); //스프링 컨테이너를 종료, ConfigurableApplicationContext 필요
}
@Configuration
static class LifeCycleConfig {
@Bean
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}}}
1. 초기화 인터페이스 (InitializingBean), 소멸 인터페이스 (DisposableBean) 사용
스프링 출시 초창기에 나온 방법이고, 지금에 와서는 더 효율적인 방법들이 등장했으니 알아만 두자.
public class NetworkClient implements InitializingBean, DisposableBean {
.
.
.
//InitializingBean : 초기화를 지원하는 메소드 afterPropertiesSet() 제공
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("초기화 연결 메세지");
}
//DisposableBean : 소멸을 지원하는 메소드 destroy() 제공
@Override
public void destroy() throws Exception {
disconnect();
}
.
.
.
출력결과 :
생성자 호출, url = null connect:https://hello-spring.dev // 의존관계 주입 완료
call:https://hello-spring.devmessage = 초기화 연결 메세지 // 초기화 완료 → 초기화 메소드 호출
16:53:35.924 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext --Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@8462f31, started on Thu Feb 15 16:53:35 KST 2024 // 스프링 컨테이너의 종료 호출
close: https://hello-spring.dev // 소멸 메소드 호출
한계점 : 초기화/소멸 메소드의 이름을 변경할 수 없다, 외부 라이브러리에 적용할 수 없다, 스프링 전용 인터페이스라서 다소 의존적인 코드이다.
2. 설정 정보에 초기화/소멸 메소드를 지정하기
1번과 가장 큰 차이점은 초기화/소멸 메소드의 이름을 자유롭게 지정할 수 있다는 것이다.
테스트코드에 있던 설정 정보를 지우고, NetworkClient 클래스 안에 아래와 같이 초기화 및 소멸메소드와 설정 정보를 담고있는 내부 클래스를 선언해주었다.
//초기화 메소드
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:https://hello-spring.dev // 의존관계 주입 완료
call:https://hello-spring.devmessage = 초기화 연결 메세지 // 초기화 메소드가 호출됨.
17:56:00.403 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext --Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@70ed52de, started on Thu Feb 15 17:56:00 KST 2024 //스프링 컨테이너의 종료 호출
NetworkClient.close close: https://hello-spring.dev //소멸 메소드가 호출됨.
설정 정보 지정 방식의 특징 :
1) 초기화/소멸 메소드의 이름을 자유롭게 지정할 수 있다.
2) 빈이 스프링 코드에 의존하지 않는다.
3) 코드가 아닌 설정 정보를 사용하기 때문에 코드의 변경이 불가능한 외부 라이브러리에도 초기화/소멸 메소드를 적용할 수 있다.(이 방식만 유일하게 외부 라이브러리에 사용이 가능하다)
3. 어노테이션 @PostConstruct, @PreDestroy 사용
최신 스프링에서 가장 많이 쓰이고 공식문서에서도 가장 권장되는 방식으로, 어노테이션 하나만 추가하면 되므로 편리하다. 위의 코드에서 init()과 close()에 PostConstruct, PreDestroy 어노테이션만 추가하고 설정 정보는 다시 원위치 시켜주었다.
@PostConstruct
public void init() {
System.out.println("NetworkClient.init");
connect();
call("초기화 연결되었습니당ㅎㅎ");
}
@PreDestroy
public void close() {
System.out.println("NetworkClient.close");
disconnect();
}
실행 결과 :
생성자 호출, url = null
NetworkClient.init connect:https://hello-spring.dev
call:https://hello-spring.devmessage = 초기화 연결되었습니당ㅎㅎ
18:21:10.436 [main] DEBUG o.s.c.a.AnnotationConfigApplicationContext --Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@70ed52de, started on Thu Feb 15 18:21:10 KST 2024 NetworkClient.close
close: https://hello-spring.dev
어노테이션 사용 방식의 장점
- 스프링에 종속적인 기술이 아니다. 스프링이 아닌 다른 컨테이너에서도 잘 동작할 수 있다. @PostConstruct와 @PreDestroy를 추가하면 import문에 import jakarta.annotation.PostConstruct;import jakarta.annotation.PreDestroy; 가 추가되는데 이는 자바에서 공식적으로 지원하는 기술이라는 뜻이다.
- 어노테이션을 사용하기 때문에 @ComponentScan과 잘 어울린다.
∴ 코드 수정이 불가능한 외부 라이브러리의 초기화/종료에 접근해야 하는 것이 아니라면 어노테이션 방식을 사용하여 콜백시키자.!!
'Language > Spring Boot || Spring' 카테고리의 다른 글
스프링 빈 스코프 (1) | 2024.02.18 |
---|---|
Spring Boot에서 base64로 이미지 업로드,다운로드 기능 구현하기 (0) | 2024.02.17 |
Spring 생성자 주입을 선택해야 하는 이유 (0) | 2024.02.08 |
Spring 의존관계 주입(DI) 방법 (0) | 2024.02.07 |
스프링부트 순환참조 문제 (0) | 2024.01.31 |