- 빈 생명주기 콜백 시작
- 애플리케이션과 DB를 서버 시작 시 미리 연결을 해두거나 애플리케이션 시작 시 필요한 연결을 미리 해두기. 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요.
- 외부 네트워크에 미리 연결하는 객체를 하나 생성한다고 가정
- NetworkClient가 애플리케이션 시작 시점에 connect()를 호출해서 연결을 맺어두어야 하고, 종료되면 disConnect() 를 호출해 연결을 끊어야 함.
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.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(){
//Configu~ 는 ApplicationContext 를 상속하고 있고 close는 기본 ApplicationContext가 아닌 이것의 하위
//클래스까지 내려가야 존재
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이 됨.
- 객체 생성 당시 url이 설정되지 않으며, 객체를 생성한 다음 외부에서 수정자 주입을 통해서 setUrl()이 호출 되어야 url이 존재하게 되기 때문.
- 스프링 빈은 간단히 객체생성 -> 의존관계 주입의 라이프사이클을 가짐.
- 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 함. 어떻게 주입이 완료된 시점을 알까?
- 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점을 알려주는 다양한 기능 제공
- 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 줌
- 스프링 빈의 이벤트 라이프 사이클(싱글톤에 대한 설명)
- 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료
- 초기화 콜백 : 빈이 생성되고 의존관계 주입이 된 후에 호출
- 소멸 전 콜백 : 빈이 소멸되기 직전에 호출
- 객체의 생성과 초기화를 분리해야 함.
- 생성자는 필수 정보를 받고 메모리를 할당해서 객체를 생성하는 책임을 가짐.
- 초기화는 이렇게 생성된 값들을 활용해 외부 커넥션을 연결하는 등 무거운 동작 수행
- 생성자 안에서 무거운 초기화 작업을 함께 하는 것보다는 객체를 생성하는 부분과 초기화하는 부분을 명확하게 나누는 것이 유지보수 관점에서 좋음.
- 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우 생성자에서 처리해도 괜찮음
- 스프링은 아래의 3가지 방법으로 빈 생명주기 콜백 지원
- 인터페이스
- 설정 ㅈ어보에 초기화 메서드, 종료 메서드 지정
- @PostConstruct, @PreDestroy 애노테이션 지원
- 인터페이스
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);
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);
}
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("초기화 연결 메시지");
}
@Override
public void destroy() throws Exception {
disconnect();
}
}
- 단점
- 스프링 전용 인터페이스
- 초기화, 소멸 메서드의 이름 변경 불가
- 내가 코드를 고칠 수 없는 외부 라이브러리에 적용 불가
- 빈 등록 초기화, 소멸 메서드
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);
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 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;
}
- 장점
- 메서드 이름 자유롭게 줄 수 있음
- 스프링 빈이 스프링 코드에 의존 x
- 코드가 아니라 설정 정보를 사용하기 때문에 고칠 수 없는 외부 라이브러리에도 종료 메서드 적용 가능
- 종료 메서드 추론
- 라이브러리는 대부분 close, shutdown이라는 이름의 종료 메서드 사용
- 기본 값이 inferred로 등록 되어있고 자동으로 종료 메서드를 추론해서 호출해줌
- 따라서 따로 지정해주지 않아도 잘 동작하곤 함.
- 추론 기능 원치 않으면 공백으로 지정하면 됨.