본 게시글은 인프런 '김영한'님의 '스프링 핵심원리 - 기본편' 강의를 듣고 정리한 글입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard
데이터베이스 커넥션 풀(아직은 모르겠다..)이나, 네트워크 소켓처럼 어플리케이션 시작 시점에 필요한 연결을 미리 해두고, 어플리케이션 종료 시점에 연결을 모두 종료하는 작업을 하기 위해서 객체의 초기화와 종료 작업이 필요하다.
public class NetworkClient {
private String url; //접속하려고 하는 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(); //ac를 받기 위해서 ConfigurableApplicationContext로 받아야함
}
@Configuration
static class LifeCycleConfig{
@Bean
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-goat.com");
return networkClient;
}
}
}
이 때, ApplicationContext로 하면 ac.close()를 호출할 수 없다.
ApplicationContext는 기본적으로 닫는 메서드가 정의가 되어있어서 이렇게 수동으로 하지 않아도 된다. 따라서, ConfigurableApplicationContext로 바꾸어서 진행한다.
ApplicationContext 안에 ConfigurableApplicationContext가 존재한다.(ConfigurableApplicationContext가 ApplicationContext를 상속받음)
또한 ConfigurableApplicationContext가 AnnotationConfigApplicationContext의 상위 부모이다. 따라서 new로 생성할 수 있음
new생성자 메서드를 먼저 호출하면서 아직 url이 설정이 안되어서 그렇다.
즉, 객체를 생성한 다음에 외부에서 수정자주입(set)을 통해 url을 설정해야 url이 제대로 나온다.
스프링 빈은 (생성자 주입할 때는 아니고)
1. 객체 생성
2. 의존관계 주입
의 순으로 작동한다.
객체를 생성해 놔야, 의존관계로 무엇인가를 할 것인가 아닌가?
물론 생성자 주입같은 경우에는 예외일 것이다.
따라서, 스프링 빈은 객체를 생성한 후, 의존관계 주입이 완료가 되어야 데이터를 사용할 수 있게 된다.
그렇다면, 개발자가 어떻게 해당 과정이 다 끝났는지 알 수 있는가?
여기서 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점(데이터 준비 완료)를 알려주는 기능을 제공한다.
또한, 스프링 컨테이너가 종료되기 직전에도 소멸 콜백을 주어서, 안전하게 종료시킨다.
참고 : 객체의 생성과 초기화를 분리할 것!
유지보수 관점 때문에 그러하다.
이 때, 스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.
1. 인터페이스 (InitializingBean, DisposableBean)
2. 설정 정보에 초기화 메서드, 종료 메서드 지정
3. @PostConstruct, @PreDestory 어노테이션 지원
이미 만들었던 NetworkClient 클래스를 InitializingBean을 구현하게 끔 implements코드를 넣어주고, 메서드를 오버라이드 하면
@Override
public void afterPropertiesSet() throws Exception {
}
이는 의존관계 주입이 끝나면 진행할 코드를 담는다.
이를 이렇게 바꿔주자 (생성자에 있던 코드를 잘라 붙임)
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("초기화 연결 메세지");
}
추가했던 InitializingBean과 더불아 DisposableBean 또한 구현체로 implements한다.
@Override
public void destroy() throws Exception {
disconnect();
}
다음과 같이 코드를 작성하여 연결을 해제시키자.
스프링 의존관계 주입이 끝나면(초기화가 끝나면)
afterPropertiesSet 함수로 인해
connect,
call 을 하고
AnnotationContext가 완전히 close 되기 전에, (ac.close())
빈들이 하나씩 죽어나가면서 destroy가 호출된다.
@Bean(initMethod = "init", destroyMethod = "close") 처럼 초기화, 소멸 메서드를 지정할 수 있다.
아까랑 동일하게, 같은 역할을 하는 메서드이지만, 인터페이스 구현한 부분을 빼고 새로운 메서드명으로 클래스 내부에 작성했다.
public void happy(){ // 초기화 메서드
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메세지");
}
public void terrify() { // 종료 메서드
System.out.println("NetworkClient.close");
disconnect();
}
이러한 메서드를 지정한채로,
테스트코드 의 @Configuration 붙은 클래스 내부 Bean 어노테이션에 init, destroy 메서드의 속성으로 해당 메서드 이름을 부여하는 것이다.
그 코드는 다음과 같다.
@Configuration
static class LifeCycleConfig{
@Bean(initMethod = "happy", destroyMethod = "terrify" ) //메서드 속성에 해당 메서드 이름을 넣는다.
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-goat.com")
return networkClient;
}
}
라이브러리는 대부분 종료할 때, close / shutdown 이름의 종료 메서드를 호출한다.
@Bean의 destroy 메서드는 기본값이 (inferred) 로 설정이 되어있다.
이때 이 inferred("추론") 기능은 close, shutdown이라는 이름의 메서드를 자동으로 호출해준다. 즉, 이 름 그대로 종료 메서드를 추론해 호출해준다.
따라서 기본적으로 destroyMethod= 으로 적어주지 않아도 default로 inferred가 붙어있기 때문에, 따로 설정해주지 않아도 되지만, 만약 종료 메서드를 자동으로 호출해주는 것을 막고자 한다면,
@Bean(destroyMethod="")
처럼 빈 공백으로 설정하면 된다.
말 그대로이다. 이를 NetworkClient에 어노테이션으로 추가해보자.
public class NetworkClient {
private String url; //접속하려고 하는 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);
}
@PostConstruct
public void happy(){
System.out.println("NetworkClient.happy");
connect();
call("초기화 연결 메세지");
}
@PreDestroy
public void terrify() {
System.out.println("NetworkClient.terrify");
disconnect();
}
}