
김영한 강사님의 스프링 핵심 원리의 강의 내용과 자료를 이용했음을 밝힙니다.
웹에서는
데이터베이스 커넥션 풀이나 네트워크 소켓처럼 애플리케이션 시작 시점에 연결을 미리 해야한다.
=> 객체의 초기화 작업 필요
애플리케이션의 종료시점에 연결 모두 종료해야 한다.
=> 객체의 종료 작업 필요
오케이 그럼 객체 초기화, 종료 작업을 하면 되겠네
But! 밑의 코드를 잠깐 보자
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);
}
public void init(){
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메세지");
}
public void close(){
System.out.println("NetworkClient.close");
disconnect();
}
}
이게 클라이언트 쪽 코드
@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를 보면 알겠지만 생성자로 생성하고 나서 수정자 주입으로 Url을 주입해준다.
즉 1. 객체 생성 -> 2. 의존관계 주입 이런 식으로 이루어 진다는 것
(물론 생성자 주입은 같이 일어난다.)
그런데 객체생성, 의존관계 주입이 다 끝나야 위에서 말한 초기화 작업 즉 연결같은 것들을 할 수가 있다.
근데 우리가 언제 의존관계 주입 다 끝났는지 어떻게 알아?!

늘 그랬듯이 스프링이 해결해준다.
초기화, 종료 콜백 메서드라는 것을 통해 우리에게 언제 초기화하고 종료하면 되는지 알려준다.
초기화 콜백: 빈 생성, 의존관계 주입 완료 후 호출
소멸 전 콜백: 빈이 소멸되지 직전에 호출
즉
스프링 컨테이너 생성 - 스프링 빈 생성 - 의존관계 주입 - 초기화 콜백 - 사용 - 소멸전 콜백 - 스프링 종료
이런 식으로 스프링 빈의 이벤트 라이프사이클을 알 수 있다.
Q. 스프링 빈의 이벤트 라이프 사이클을 보면 초기화(종료) 콜백을 사용한 다음에 초기화(종료)를 해줘야 하는 거 아냐?
맞다.
그 초기화(종료) 작업을 초기화(종료) 콜백 함수에서 처리를 해주면 되는 것이다.
Q. 걍 생성자 주입하고 생성자에 초기화 내용도 만들어버리면 간단한거 아님?
초기화라는 것이 외부와 연결을 진행하는 것이다 보니 무거운 동작으로 분류된다.
그러다 보니 생성자 부분에 초기화 부분까지 넣어버리면 명확하지도 않고 유지보수 관점에서 어렵다.
물론 초기화가 정말 간단하다면 넣어도 되겠다.
스프링은 3가지의 방법으로 빈 생명주기 콜백을 지원한다.
- 인터페이스(InitializingBean, DisposableBean)
- 설정 정보에 초기화 메서드, 종료 메서드 지정
- @PostConstruct, @PreDestroy 애노테이션
김영한 강사님의 강의 특징
-> 맨 마지막 방법이 가장 유용하고 추천되는 방법이다. ㅎㅎ
그래도 앞의 2개의 방법도 알아는 보자.
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);
}
@Override
public void afterPropertiesSet() throw Exception{
System.out.println("NetworkClient.afterPropertiesSet");
connect();
call("초기화 연결 메세지");
}
@Override
public void destroy() throw Exception{
System.out.println("NetworkClient.destroy");
disconnect();
}
}
자 이렇게 클라이언트 쪽에서 InitializingBean을 상속해서 afterPropertiesSet()을, DisposableBean을 상속해서 destroy()를 사용해서 각각 초기화, 소멸 콜백 메서드를 사용할 수 있다.
그러면 맨 위에서 돌렸던 test를 이 클라이언트 코드를 가지고 돌려보면
생성자 호출, url = null
NetworkClient.afterPropertiesSet
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
13:24:49.043 [main] DEBUG
org.springframework.context.annotation.AnnotationConfigApplicationContext -
Closing NetworkClient.destroy
close + http://hello-spring.dev
이런 식으로 초기화, 종료 시점에 콜백 함수가 잘 실행되는 것을 확인할 수 있다.
하지만 단점도 존재한다.
상속한 InitializingBean, DisposableBean 인터페이스는 스프링 전용 인터페이스이기에 코드가 스프링 전용 인터페이스에 의존하게 된다.
이렇게 의존하게 되면 초기화 소멸 메서드 이름을 변경 불가능하다.
근데 뭐 내 생각에 이정도 단점은 괜찮다 하지만 큰게 하나 남아있는데
내가 코드를 고칠 수 없는 외부라이브러리에 적용이 불가능하다는 것이다.
그렇기에 비추한다.
Bean(initMethod="초기화 메소드 이름", destroyMethod="소멸 메소드 이름") 이런 식으로 Bean 뒤에 초기화 메소드, 소멸 메소드를 지정해주면 된다.
코드로 예시를 들어보자면
@Configuration
static class LifeCycleConfig {
@Bean(initMethod="init", destroyMethod="close") //여기 변함!!
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
이렇게 설정 정보를 만들고
클라이언트에 init, close 함수를 만들어주면 된다.
public class NetworkClient{
private String url;
public NetworkClient() {..}
public void setUrl(String url) {..}
public void connect() {..}
public void call(String message){..}
public void disconnect(){..}
public void init() { // 여기!~
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메세지");
}
public void close() { // 여기!~
System.out.println("NetworkClient.close");
disconnect();
}
}
이렇게 하고 test를 돌리면
생성자 호출, url = null
NetworkClient.init
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
13:33:10.029 [main] DEBUG
org.springframework.context.annotation.AnnotationConfigApplicationContext -
Closing NetworkClient.close
close + http://hello-spring.dev
이렇게 잘 나오는 것을 확인할 수 있다.
자 위의 인터페이스 방법과 다르게 메서드 이름을 자유롭게 주며 스프링 코드에 의존하지 않기에 코드를 고칠 수 없는 외부 라이브러리에도 초기화 종료 메서드를 사용할 수 있다:)
가장 추천하는 방법
초기화 메서드 만들고 @PostConstruct 붙이고
소멸 메서드 만들고 @PreDestroy 붙이면 끝!
public class NetworkClient{
private String url;
public NetworkClient() {..}
public void setUrl(String url) {..}
public void connect() {..}
public void call(String message){..}
public void disconnect(){..}
@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: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
19:40:50.269 [main] DEBUG
org.springframework.context.annotation.AnnotationConfigApplicationContext -
Closing NetworkClient.close
close + http://hello-spring.dev
이렇게 깔끔하게 나온다.
스프링에서 가장 권장하는 방법이다.
간단하기도 하지만 애노테이션 import 문을 보면 javax.annotation.PostConstruct 이렇게 되있는 것을 확인할 수 있다.
javax, 즉 스프링에 종속적이지 않고 자바 표준이다.
하지만.. 얘도 단점이 있다. 바로 첫 번째 인터페이스의 단점처럼 외부라이브러리에 적용이 안된다.
외부 라이브러리를 초기화, 종료를 해야 한다면 두 번째, 빈을 이용한 방법을 사용하자
즉 초기화, 종료를 하기 위해 콜백 함수를 사용해야 한다.
왠만하면 애노테이션(@PostConstruct, @PreDestroy)을 사용하는 것을 권장한다.
하지만 이 방식은 외부라이브러리에 적용이 안되니 외부라이브러리를 초기화, 종료해야하면 빈 등록 초기화, 소멸 메서드 지정을 하자.