@Bean의 라이프사이클, 그리고 콜백
해당 내용은 '스프링 입문을 위한 자바 객체 지향의 원리와 이해'와 인프런 김영한님의 '스프링 핵심 원리 - 기본편' 강의를 참고하였습니다.
예를 들어 DB 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면 객체의 초기화와 종료 작업이 필요하다.
한 번 테스트로 외부 커넥션이랑 연결한다고 가정하고 (실제로는 메세지만 띄울꺼지만..) 테스트 코드를 작성해보자.
NetworkClient.java
public class NetworkClient {
private String url;
public NetworkClient() {
System.out.println("생성자 호출, url = " + url);
connect();
call("initializing msg");
}
public void setUrl(String url) {
this.url = url;
}
// 서비스 시작시 호출
public void connect(){
System.out.println("connect : " + url);
}
public void call(String msg){
System.out.println("call : " + url + " / msg : " + msg);
}
// 서비스 종료시에 호출
public void disConnect(){
System.out.println("close " + url);
}
}
BeanLifeCycleTest.java
public class BeanLifeCycleTest {
@Test
public void lifeCycleTest(){
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeCycleConfig.class); // close 기능을 사용하기 위해서 ConfigurableApplicationContext를 사용하였음.
NetworkClient client = applicationContext.getBean(NetworkClient.class);
applicationContext.close();
}
@Configuration
static class LifeCycleConfig{
@Bean
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://Network-Tst.com");
return networkClient;
}
}
}
실행해보면 다음과 같은 결과가 나온다.
생성자 호출, url = null
connect : null
call : null msg : initializing msg
우리가 setUrl
을 했음에도 불구하고 null 이 나온다.
잘 생각해보면 우리가 객체를 생성하는 시점에는 url이 세팅이 안되어 있고, 객체를 생성하고 난 뒤에 url을 set 하기에 이런 일이 발생한 것이다.
스프링 빈은 다음과 같은 라이프사이클을 갖는다.
객체 생성 -> 의존 관계 주입
(생성자 주입은 제외한다. 생성자 주입은 생성자가 생성됨과 동시에 빈을 주입하기에.. 여기서는 setter나 field injection을 이야기 한다)
스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에서야 데이터를 사용할 수 있게 된다.
따라서 초기화 작업은 의존관계 주입이 다 마무리 된 이후에 호출해야만 한다.
그런데, 개발자가 이 시점을 어떻게 알 수 있을까?
스프링 빈의 이벤트 라이프사이클을 알아보자 (싱글톤 기준)
- 스프링 컨테이너 생성
- 스프링 빈 생성
- 의존관계 주입
- 초기화 콜백
- 사용
- 소멸전 콜백
- 스프링 종료
위에서 볼 수 있듯이 스프링은 콜백 메서드를 통해서 특정 시점을 알려주는 역할이 있다. 이를 통해서 개발자는 시점에 대해 알 수 있는 것이다.
스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 다룰 수 있도록 지원한다.
한 번 알아보도록 하자.
인터페이스 InitialzingBean, DisposalbleBean
사용법은 간단하다
기존의 클래스에 다음과 같이 인터페이스를 상속받는다.
물론 그에 따른 구현 메서드도 추가해준다.
InitializingBean
에는 afterPropertiesSet()
이라는 메서드가,
DisposableBean
에는 destroy()
라는 메서드가 구현이 필요 된다.
NetworkClient.java
public class NetworkClient implements InitializingBean, DisposableBean {
...
// propreties의 Setting이 끝나면 (의존관계 주입이 끝나면) 호출
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("initializing msg");
}
// bean이 소멸될 때 호출하겠다.
@Override
public void destroy() throws Exception {
disConnect();
}
}
주석에도 적어놓았듯, 해당 메서드들은 위의 빈의 이벤트 라이프 사이클에서 초기화 콜백과 소멸 콜백에 해당되는 메서드들이다.
자 그럼 이제 위의 코드처럼 우리가 기존에 의존관계가 주입되기전에 호출되었던 메서드들을 초기화 콜백에서 호출하고, 의존관계가 종료될때 호출되어야 했던 메서드를 소멸 콜백에서 호출해보자.
connect : http://Network-Tst.com
call : http://Network-Tst.com / msg : initializing msg
00:16:58.523 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@33dd6bed, started on Wed Mar 30 00:16:58 KST 2022
close http://Network-Tst.com
다음과 같이 우리가 setter에서 세팅한 url이 들어온 것을 알 수 있고, 빈이 소멸될 때 close 되는 것을 역시 확인할 수 있다.
그러나 이 인터페이스에도 단점이 존재한다.
- 해당 인터페이스는 스프링 전용 인터페이스이다. 해당 코드는 스프링 전용 인터페이스에 의존한다.
- 초기화, 소멸 메서드의 이름을 바꿀 수 없다.
- 외부라이브러리에 적용할 수 없다. (내가 직접적으로 고칠 수 있는 것이 아니기 때문.)
이러한 인터페이스를 사용하는 방법은 스프링 초기(2003년..)에나 나온 것들이고, 지금은 더 좋은 방법이 있어서 거의 사용되지 않는다고 한다.
빈 등록 초기화, 소멸 메서드
빈을 등록하는 시점에 어떤게 초기화이고, 어떤게 소멸인지를 지정할 수 있는 방법이다.
다음과 같이 어노테이션을 붙여주기만 하면 된다.
@Bean(initMethod="init", destroyMethod="close")
이렇게 하면 다음과 같은 이점을 얻을 수 있다.
- 메서드 이름이 강제되지 않는다.
- 스프링 빈이 스프링 코드에 의존하지 않는다.
- 코드가 아닌 설정 정보를 사용하는 것이기에, 외부 라이브러리에도 초기화, 종료 메서드를 지정할 수 있다.
자 기존 코드에 적용시켜보도록 하자.
NetworkClient.java
public class NetworkClient {
...
public void init() throws Exception {
System.out.println("NetworkClient.init");
connect();
call("initializing msg");
}
public void close() throws Exception {
System.out.println("NetworkClient.close");
disConnect();
}
}
아까 사용하던 메서드들의 이름을 바꾸고, 더이상 상속받지 않기 때문에 위의 @Override
를 뗴주었다.
그리고 호출하는 부분에서 다음과 같이 바꿔준다.
BeanLifeCycleTest.java
@Test
public void lifeCycleTest(){
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
NetworkClient client = applicationContext.getBean(NetworkClient.class);
applicationContext.close();
}
@Configuration
static class LifeCycleConfig{
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://Network-Tst.com");
return networkClient;
}
}
Config 클래스에서 Bean을 등록할때 초기화 콜백 메서드, 소멸 콜백 메서드에 대해 적어준다.
그리고 실행해보자.
NetworkClient.init
connect : http://Network-Tst.com
call : http://Network-Tst.com / msg : initializing msg
00:35:30.817 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@bfea0460, started on Wed Mar 30 00:35:30 KST 2022
NetworkClient.close
close http://Network-Tst.com
정상적으로 잘 동작하는 것을 확인할 수 있다.
종료 메서드의 '추론' 기능
@Bean
의destroyMethod
에는 특별한 기능이 있다.
원본 코드를 확인 해보면 다음과 같이 적혀있는 것을 볼 수 있는데,String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
여기서 보이는
INFER_METHOD
를 타고 들어가면 다음과 같이 적혀있는 것을 볼 수 있다.public static final String INFER_METHOD = "(inferred)";
즉,
destroyMethod
의 기본값이inferred
(추론) 값이라는 건데, 이 기능은 보통 라이브러리들의 종료 메서드 명인close
나shutdown
에 해당되는 메서드들을 자동으로 호출해준다.따라서 직접 스프링 빈으로 등록만 해주면, 종료 메서드는 따로 적어주지 않아도 된다.
이 추론기능을 사용하고 싶지않다면
destroyMethod
의 값을 공백(""
)으로 주면 된다.
Annotation @PostConstruct, @PreDestroy
결론만 말하자면...이걸 쓰면 된다!
바로 코드에 적용해보자.
NetworkClient.java
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class NetworkClient {
...
@PostConstruct
public void init() throws Exception {
System.out.println("NetworkClient.init");
connect();
call("initializing msg");
}
@PreDestroy
public void close() throws Exception {
System.out.println("NetworkClient.close");
disConnect();
}
}
더 쉽다! 이렇게 어노테이션만 더해주자.
결과는 동일하다.
NetworkClient.init
connect : http://Network-Tst.com
call : http://Network-Tst.com / msg : initializing msg
00:55:54.404 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@df47851, started on Wed Mar 30 00:55:54 KST 2022
NetworkClient.close
close http://Network-Tst.com
자 그러면 이 어노테이션의 특징에 대해 알아보자.
- 스프링에서 무려 '권장'하는 방법
- import되는 패키지를 보면
javax.annotation
으로 시작한다. 이는 스프링에 종속되는 것이 아니라 JSR-250 이라는 자바 표준이라는 것을 의미한다. - 고로 스프링이 아닌 다른 컨테이너에서도 잘 작동한다.
- 컴포넌트 스캔과 잘 어울린다.
- 단점은...외부 라이브러리에 사용을 하지 못한다는 것이다. 그러니 외부라이브러리에 콜백을 적용할 일이 있다면 위에서 배운
@Bean
의initMethod
와destroyMethod
를 이용하도록 하자.
Author And Source
이 문제에 관하여(@Bean의 라이프사이클, 그리고 콜백), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@namkun/Bean의-라이프사이클-그리고-콜백저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)