[Spring] 핵심원리 기본편 - 빈 생명주기 콜백
Java & Spring

[Spring] 핵심원리 기본편 - 빈 생명주기 콜백

빈 생명주기 콜백은 간단하게 얘기하면 객체가 생성되고 종료될때 어떤 내부 메서드를 호출해주는 기능을 말한다. 이번 챕터에서는 빈 생명주기 콜백에 대해 알아본다.

 

빈 생명주기 콜백 시작

보통 애플리케이션 개발을 할 때 데이터베이스 커넥션 풀이나 네트워크 소켓처럼 애플리케이션 시작 전에 필요한 연결을 미리 해두고, 종료 시점에 연결을 종료해야 하는 작업을 하게 된다. 이때 객체를 초기화하고 종료시키는 작업이 필요하다. 이러한 초기화 & 종료 작업은 어떻게 하는지 알아보자.

 

이번 예제는 외부 네트워크에 미리 연결하는 개체를 생성해본다. (실제로 연결하진 않고 문자열 출력만)

애플리케이션 시작 시점에 'connect()'를 호출해서 연결을 맺고, 'disConnect()'를 호출해서 연결을 끊는다.

 

아래와 같이 코드를 짜보자.

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 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;
		}
	}
}

처음에 LifeCycleConfig를 통해 스프링 빈이 등록되면서 NetworkClient 빈의 생성자가 실행된다. 그러면서 아래와 같이 출력된다.

그 이후에 setUrl로 url을 넣어줬다. url은 처음에 생성자가 호출되었을 때는 없었기 때문에 null값으로 출력되었고 그 뒤에 url을 setting 해줬기 때문에 그제서야 설정이 된다.

 

여기서 보면 알 수 있듯이, 스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 후에야 필요한 데이터를 사용할 수 있는 준비가 된다. 따라서 초기화 작업은 의존관계 주입이 완료되고 난 다음에 호출해야 한다. 그럼 개발자가 의존관계 주입 완료 시점을 어떻게 알 수 있을까?

 

스프링이 알려준다.

스프링은 의존관계 주입이 완료되면 콜백 메서드를 통해 스프링 빈에게 초기화 시점을 알려주는 기능을 제공한다.

또한 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다.

 

스프링 빈의 이벤트 라이프사이클은 다음과 같다.

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료

 

그리고 스프링은 다양한 방식으로 생명주기 콜백을 지원한다. 이제 어떤 방식이 있는지 3가지를 밑에서 알아본다.


인터페이스 InitializingBean, DisposableBean

인터페이스로 초기화, 소멸 콜백을 받는 방법을 알아보자.

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 disconnect() {
		System.out.println("close: " + url);
	}

	// 의존관계 주입이 끝나면 호출하는 메서드. InitializingBean 인터페이스에서 오버라이드
	@Override
	public void afterPropertiesSet() throws Exception {
		connect();
		call("초기화 연결 메시지");
	}

	// 스프링 컨테이너가 종료될 때 호출되는 메서드. DisposableBean 인터페이스에서 오버라이드
	@Override
	public void destroy() throws Exception {
		disconnect();
	}
}
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;
		}
	}
}

생성자 호출 시점에는 url이 없고,

생성자 호출하고 의존관계 주입도 완료 된 뒤에 afterPropertiesSet() 메서드 호출을 통해 초기화가 된다.

이후에 destroy() 메서드를 통해 소멸전에 disconnect()가 호출되는 것을 알 수 있다.

 

이 방법의 단점

1. 스프링 전용 인터페이스이기 때문에 스프링 인터페이스에 의존한다.

2. 초기화, 소멸 메서드의 이름을 변경할 수 없다.

3. 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.

 

그래서 이 방식은 이전에 스프링 초창기에 사용했던 방식이고, 요즘은 잘 사용하지 않는다.


빈 등록 초기화, 소멸 메서드

public class NetworkClient {
	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 disconnect() {
		System.out.println("close: " + url);
	}

	// 의존관계 주입이 끝나면 호출하는 메서드. InitializingBean 인터페이스에서 오버라이드
	public void init() {
		connect();
		call("초기화 연결 메시지");
	}

	// 스프링 컨테이너가 종료될 때 호출되는 메서드. DisposableBean 인터페이스에서 오버라이드
	public void 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;
	}
}

두번째 방식은 초기화 콜백, 소멸전 콜백 메서드를 정의해두고, 빈 등록할 때 @Bean(initMethod = "init", destroyMethod = "close")와 같이 옵션을 주는 방법이다. 결과물은 첫번째 방법때와 같다.

 

 이 방식의 장점

1. 메서드 이름을 자유롭게 줄 수 있다.

2. 스프링 빈이 스프링 코드에 의존하지 않는다.

3. 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.

 

이 방식에서 destroyMethod 옵션에는 특별한 기능이 있다.

바로 따로 옵션 지정을 해주지 않더라도 'close', 'shutdown' 이라는 이름의 메서드가 있으면 자동으로 종료 메서드로 사용한다는 것이다.

만약 이 기능을 사용하기 싫으면 'destroyMethod=""' 와 같이 사용하면 된다.


애노테이션 @PostConstruct, @PreDestroy

요즘 가장 많이 사용하는 방식이다.

// 의존관계 주입이 끝나면 호출하는 메서드. InitializingBean 인터페이스에서 오버라이드
@PostConstruct
public void init() {
	connect();
	call("초기화 연결 메시지");
}

// 스프링 컨테이너가 종료될 때 호출되는 메서드. DisposableBean 인터페이스에서 오버라이드
@PreDestroy
public void close() {
	disconnect();
}

요렇게 @PostConstruct, @PreDestory 애노테이션만 붙여주면 된다!

 

특징은

1. 최신 스프링에서 가장 권장하는 방법

2. 패키지를 보면 'javax.annotation.PostConstruct'다. 스프링에 종속적이지 않고 자바 표준이다. 따라서 스프링이 아닌 컨테이너에서도 잘 동작한다.

3. 컴포넌트 스캔과 잘 어울린다.

4. 유일한 단점은 외부 라이브러리에 적용하지 못한다는 것이다. 필요하다면 @Bean의 기능을 사용하자.


정리

빈 생명주기 콜백에 대해 알아봤다.

스프링 빈이 등록되고 소멸되는 과정이 어떻게 나누어져 있는지 알게 되었다.

728x90
반응형