Service로직을 구현하는 구현체 Bean도 있겠지만 web application에서 DB connection pool이나 socket과 같이 application 시작 시점에 미리 생성해두고 종료 시점에 종료시키는 작업을 진행해야하는 경우도 있습니다. spring에서는 이 작업을 어떻게 실행하고 있는지 알아보겠습니다.
DB에 연결하는 예시를 코드로 살펴보겠습니다.
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);
}
}
DB를 연결하는 NetworkClient class를 만들고 setUrl을 통해 url을 입력해주고 DB연결하는 상황을 가정해보겠습니다.
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://www.test.com");
return networkClient;
}
}
}
그리고 Test code를 작성해서 실행해보면
다음과 같이 출력이된다. java를 아는 분들이라면 당연한 결과라고 생각하실 수 있습니다. NetworkClient의 bean이 등록되는 과정을 보면
이렇기 때문에 null이 print되는것이 당연합니다. 그렇다면 객체가 생성되는 시점에 connect를 하고 싶어도 url정보가 null이므로 DB와 connect를 맺을수가 없습니다.
스프링 빈은 우리가 공부해왔던 내용 그대로
의 순서로 진행이됩니다. 그렇다면 우리는 connect()와 call()을
순서로 진행해야합니다. 그렇다면 의존 관계 주입(='setUrl()')이 끝났음을 개발자에게 알려주고 개발자는 그 시점에 connect(), call()과 같은 초기화 작업을 진행하면 됩니다. spring에서는 bean에 의존 관계 주입이 완료되었음을 callback method를 통해서 알려주는데요. 이 콜백을 포함하는 bean의 생명주기(life cycle)은
이러한 작업을 위해서 spring에서 3가지의 방법이 있는데요
순서대로 알아보겠습니다.
Spring에서 지원하는 Interface인데요.
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() throws Exception {
System.out.println("NetworkClient.afterPropertiesSet");
connect();
call("초기화 연결 메시지");
}
@Override
public void destroy() throws Exception {
System.out.println("NetworkClient.destroy");
disconnect();
}
}
아까 만들었던 NetworkClient class에 InitializingBean, DisposableBean 인터페이스를 상속받고 afterPropertiesSet()과 destroy()를 오버라이드하여 사용합니다. 아까 생성자에 있던 connect()와 call()을 afterPropertiesSet()에 넣고 disconnect()는 destroy()에 넣습니다. 이렇게 test 코드를 실행시키시면
아까와는 다르게 생성자가 호출되었을때는 null이지만 의존 관계가 모두 주입되고(setUrl이 실행되어지고) connect()와 call() 그리고 disconnect()까지 실행이 되었습니다.
이 방법은 spring에서 지원하는 방법이지만 단점이 있는데요
위와 같은 단점으로 사용하기가 어렵습니다... 다음 방법을 알아보겠습니다.
빈을 등록할때 옵션을 주어서 사용하는 방법인데요
@Bean(initMethod = "초기화 메서드명", destroyMethod = "소멸 메서드명")
이렇게 다음과 같이 옵션을 주어서 사용할 수 있고 "메서드명"은 동일하게 작성해주셔야합니다.
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();
}
}
다음과 같이 init과 close로 작성한 후
@Configuration
static class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://www.test.com");
return networkClient;
}
}
test 코드의 bean의 옵션을 주어 실행해보겠습니다.
위 방식과 동일하게 적용된것을 확인할 수 있습니다.
bean에 옵션을 주어서 사용하는 방식은
위와 같이 bean에 옵션을 주는 방법도 좋지만 java에서 제공하는 annotation을 사용하는 방법도 있습니다.
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("초기화 연결 메시지");
}
@PreDestroy
public void close() {
System.out.println("NetworkClient.close");
disConnect();
}
}
코드를 수정한 후 아까 Bean에 주었던 옵션은 모두 제거하셔도 됩니다. 이렇게 코드도 간단해지고
결과도 똑같이 실행되어집니다.
@PostConstruct, @PreDestory를 사용하는 방법은