데이터베이스 커넥션 풀이나 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고 종료 시점에 연결을 끊는 작업을 수행하려면 빈 객체의 초기화와 종료가 필요하다.
** DB connection pool : 웹 애플리케이션 서버와 데이터베이스를 연결하는 것에 너무 많은 비용이 들어서 데이터베이스와의 커넥션을 미리 만들어놓고 pool에 저장한 후, 필요할 때마다 꺼내서 사용하고 반환하는 방법
위부 네트워크와 연결하는 빈 객체를 생성한다고 생각하면, 빈 객체 초기화는 "외부 네트워크와 연결"이고 빈 객체 종료는 "외부 네트워크와 연결 끊음" 이다.
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(); //스프링 컨테이너를 종료, ConfigurableApplicationContext 필요 } // @Configuration static class LifeCycleConfig { @Bean public NetworkClient networkClient() { NetworkClient networkClient = new NetworkClient(); //생성자 호출 networkClient.setUrl("http://hello-spring.dev"); //url 주입 return networkClient; } } }
생성자를 호출한 후에 connect()를 하고 setUrl()을 통해 url을 주입하기 때문에 당연히 네트워크 연결이 되지 않는다.
생성자에 파라미터로 url을 전달해주는 방법도 있겠지만, 이는 단일 책임 원칙에 어긋난다. 빈 생성, 초기화는 각각의 다른 책임이다. 빈 객체의 초기화는 스프링 빈이 생성되고 의존관계 주입이 모두 끝난 후 실행되어야 한다.
그렇다면 개발자는 스프링 빈이 생성되고 의존관계 주입까지 끝난 그 시점을 알아야 초기화를 진행할 수 있다.
스프링은 의존관계 주입이 완료되면 콜백 매서드를 통해서 완료 시점을 알려주고 스프링 컨테이너 종료 전에는 소멸 콜백 매서드를 통해 종료 시점을 알려준다.
- 스프링 빈의 이벤트 라이프사이클
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료
++ 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
++ 소멸전 콜백: 빈이 소멸되기 직전에 호출
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(); } }
public class BeanLifeCycleTest { @Test public void lifeCycleTest() { ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class); NetworkClient client = ac.getBean(NetworkClient.class); ac.close(); //스프링 컨테이너를 종료, ConfigurableApplicationContext 필요 } // @Configuration static class LifeCycleConfig { @Bean(initMethod = "init", destroyMethod = "close") //매서드 지정!!!!!!!!!!!!!! public NetworkClient networkClient() { NetworkClient networkClient = new NetworkClient(); //생성자 호출 networkClient.setUrl("http://hello-spring.dev"); //url 주입 return networkClient; } } }
- destroyMethod는 생략 가능하다. 보통 라이브러리들은 종료매서드로 close, shutdown의 이름으로 매서드를 사용하는데, 이를 자동으로 호출해주기 때문이다.
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); } // @PostConstruct public void init() { System.out.println("NetworkClient.init"); connect(); call("초기화 연결 메시지"); } // @PreDestory public void close() { System.out.println("NetworkClient.close"); disConnect(); } }
public class BeanLifeCycleTest { @Test public void lifeCycleTest() { ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class); NetworkClient client = ac.getBean(NetworkClient.class); ac.close(); //스프링 컨테이너를 종료, ConfigurableApplicationContext 필요 } // @Configuration static class LifeCycleConfig { @Bean public NetworkClient networkClient() { NetworkClient networkClient = new NetworkClient(); //생성자 호출 networkClient.setUrl("http://hello-spring.dev"); //url 주입 return networkClient; } } }
- 스프링에서 권장하는 방법이다.
import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy;
스프링에 종속적인 기술이 아니라 자바의 기술이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
- 하지만, 코드를 고칠 수 없는 외부 라이브러리 빈을 초기화, 종료해야 하면 @Bean의 initMethod, destroyMethod 를 사용해야 한다.