스프링 핵심 원리 - 7일차(빈 생명주기 콜백)
디비와 연동하거나 네트워크 소켓처럼 미리 연결을 해야지 바로바로 응답할 수 있고, 서버 다운될때 한번에 종료되는것보다 미리 안전하게 종료가 더 좋다고 함 이러한 기능을 스프링에서 어떻게 하는지 알아본다.
간단한게 네트워크 미리 연결하는 객체 예제를 통해 알아보면,
package hello.core.lifecycle;
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);
}
}
해당 코드는 객체가 생성될때 생성자에서 생성 후 연결을 해준다.
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() {
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
NetworkClient client = ac.getBean(NetworkClient.class);
ac.close();
}
@Configuration
static class LifeCycleConfig {
@Bean//(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("heep://hello-spring.dev");
return networkClient;
}
}
}

위 결과는 당연히 생성자 호출할때는 객체 생성하는 단계니까 url 연결 없어서 null 값이 뜸
스프링 빈은 객체 생성 -> 의존관계 주입 이라는 라이프사이클을 가진다
초기화 작업은 의존관계 주입이 끝나고 해서 해야함
객체의 생성과 초기화를 분리를 해줘야 한다.(이 방식이 단일책임 원칙에 더 준수함 생성은 생성만 담당)
만약 초기화 작업이 단순할경우 생성자에서 한번에 처리하는 게 나을 수 있지만 그렇게 하는 상황이 매우 많지는 않음
이렇게 빈 생멍주기 콜백하는것을 스프링에서는 3가지 방식을 지원해준다
인터페이스(InitializingBean, DisposableBean)
설정 정보 초기화 메서드, 종료 메서드 지정
@PostConstruct, @PreDestroy 어노테이션
이렇게 3가지에 대해서 알아본다.
1. 인터페이스(InitializingBean, DisposableBean) 방식
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);
}
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("초기화 연결 메세지");
}
@Override
public void destory() throws Exception {
disConnect();
}
}

InitializingBean에 afterPropertiesSet() 은 메서드 초기화를 지원하고
DisposableBean은 destory() 메서드 소멸을 지원함
초기화 메서드가 주입 이후에 적절하게 호출됨 소멸 메서드도 호출됨
-> 이러한 방식은 거의 잘 안쓰임
why? ) 스프링 전용 인터페이스에 의존하고 초기화, 소멸 메서드 이름 변경이 불가능하다. 그리고 외부 라이브러리에는 적용 할 수 없음
2.설정 정보 초기화 메서드, 종료 메서드 지정
이방법은 쉽다
@Bean(initMethod = "초기화메서드 이름", destroyMethod = "종료 메서드 이름" )이런식으로 적으면된다.
NetworkClient 클래스를 원래대로 바꾼후에 @Bean 옆에 위 에시처럼 추가한다.
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() {
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
NetworkClient client = ac.getBean(NetworkClient.class);
ac.close();
}
@Configuration
static class LifeCycleConfig {
@Bean (initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("heep://hello-spring.dev");
return networkClient;
}
}
}

이건 1번 방식과 다르게 메서드 이름을 내가 정할 수 있고 스프링 코드에 의존하지 않아도 된다.
또 외부 라이브러리에서도 적용이 가능하다.
또한 위에 destroyMethod에는 재밌는 기능하나가 숨겨져 있다.
destoryMethod의 기본값은 interred(추론)으로 등록되어져있다. 이 기능은 close, shutdown 이름이 있으면 해당 메서드를 자동으로 호출해준다. 그래서 따로 적지 않아도 잘 동작함
만약 추론 기능을 사용하기 싫으면 destroyMethod=""처럼 빈 공백을 저장하면 된다.
3.@PostConstruct, @PreDestroy 어노테이션
최근 스프링이 가장 권장하는 방법이다.
package hello.core.lifecycle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
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);
}
// @Override
// public void afterPropertiesSet() throws Exception {
// connect();
// call("초기화 연결 메세지");
// }
//
// @Override
// public void destroy() throws Exception {
// disconnect();
// }
@PostConstruct
public void init() { // 의존관계 호출이 끝나면
System.out.println("NetworkClient.init");
connect();
call("초기화 연겲 메세지");
}
@PreDestroy
public void close() {
System.out.println("NetworkClient.close");
disconnect();
}
}

@Bean은 이제 다시 원상태로 돌린 후에 초기화 메서드 종료 메서드 위에 각각의 어노테이션을 붙이면 된다
위 패키지를 보면 javax로 적혀있는데 이건 자바에 공식적으로 지원이 되는거다. 그래서 스프링 뿐만 아니라 다른 컨테이너에서도 잘 동작함
단점은 외부 라이브러리에 적용을 못하는데 외부 라이브러리에 적용할때만 2번방식을 사용하면됨