스프링을 사용하다 보면 객체의 초기화와 종료 시점에 작업을 해줘야 할 때가 있다. 예를 들어 네트워크 연결을 열거나 닫아야 할 때, 또는 파일이나 데이터베이스 리소스를 정리해야 할 때이다.
어플리케이션을 실행할 때 꼭 필요한 작업들이 있다.
이처럼 객체 생성 -> 의존관계 주입 -> 초기화 -> 사용 -> 종료 전 정리 과정이 필요하다. 스프링은 이 과정을 자동으로 관리할 수 있는 콜백 메커니즘을 제공한다.
스프링 빈은 아래와 같은 순서로 동작한다.
1. 스프링 컨테이너 생성
2. 스프링 빈 생성
3. 의존관계 주입
4. 초기화 콜백
5. 빈 사용
6. 소멸 전 콜백
7. 스프링 종료
🌟 핵심은 초기화 작업은 의존관계 주입 이후에 실행돼야 한다는 것이다!!!
생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면 초기화는 이렇게 생성된 값들을 활용해 외부 커넥션을 연결하는 등 무거운 동작을 수행한다.
조금 더 이해하기 쉽도록 아래와 같은 예시를 보며 이해해보자!
[NetworkClient 클래스]
public NetworkClient() {
System.out.println("생성자 호출, url = " + url);
connect(); // 문제: 아직 URL이 주입되지 않음
}
객체 생성하는 단계에는 url이 없고, 객체를 생성한 다음에 외부에서 수정자 주입을 통해 setUrl()이 호출되어야 url이 존재하게 된다. 즉 url이 아직 주입되기 전이라 null 상태에서 connect()가 실행되는 것이다.
따라서 초기화는 생성자가 아니라 따로 분리해서 처리해야한다.
스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다. 즉 위에서 언급한대로 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 한다. 그런데 개발자가 의존관계 주입이 모두 완료된 시점을 어떻게 알 수 있을까?
✅ 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공한다. 또한 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다. 따라서 안전하게 종료 작업을 진행할 수 있다.
스프링이 제공하는 인터페이스를 직접 구현해서 초기화/소멸 메서드를 정의하는 방법이다.
afterPropertiesSet()은 빈이 생성되고 의존관계 주입이 끝난 뒤 자동으로 호출된다.destroy()는 컨테이너가 종료되기 전에 호출된다.public class NetworkClient implements InitializingBean, DisposableBean {
private String url;
public void setUrl(String url) {
this.url = url;
}
@Override
public void afterPropertiesSet() {
connect();
call("초기화 메시지");
}
@Override
public void destroy() {
disconnect();
}
}
👉 단점
요즘은 거의 사용하지 않는 방식이다!!
@Bean 어노테이션에서 초기화/종료 메서드의 이름을 지정해준다.
빈 클래스 내부에 init(), close() 등의 메서드를 자유롭게 만들고 설정에서 지정하는 방식이다.
public class NetworkClient {
public void init() {
connect();
call("초기화 메시지");
}
public void close() {
disconnect();
}
}
@Configuration
static class Config {
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
NetworkClient client = new NetworkClient();
client.setUrl("http://hello.dev");
return client;
}
}
👉 장점
👉 단점
Tip : destroyMethod는 기본값이 "inferred"로 되어 있어서 메서드 이름이 close나 shutdown이면 자동으로 호출된다.
→ 굳이 destroyMethod = "close" 안 써도 된다.
최신 스프링에서 가장 권장하는 방법이다.
자바 표준 애노테이션 @PostConstruct, @PreDestroy를 사용하면 간단히 초기화/소멸을 구현할 수 있다.
public class NetworkClient {
@PostConstruct
public void init() {
connect();
call("초기화 메시지");
}
@PreDestroy
public void close() {
disconnect();
}
}
👉 장점
👉 단점
@PostConstruct, @PreDestroy 사용!@Bean(initMethod, destroyMethod) 사용!