빈 생명주기에 대해서 알아보겠습니다.
데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고
애플리 케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요합니다.
package hello.core.lifecycle;
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);
}
}
이와 같이 예제 코드를 짜보았습니다.
실제 네트워크에 접속하는건 아니고, 문자열로 출력하게끔 만들었습니다.
이렇게 하면 lifeCycleTest()에서는 LifeCycleConfig 클래스를 환경으로 설정하고
LifeCycleConfig는 아까 작성한 NetworkClient 생성자 생성, 속성 설정한 후 반환합니다.
그리고 lifeCycleTest()내에 NetworkClient 객체인 client에 빈 정보를 심어 테스트를 한 후
스프링 컨테이너를 종료하는 로직을 만들었습니다.
하지만 이렇게 설정을 하면
이와같이, url이 null로 나옵니다.
그 이유는 NetworkClient 클래스에서 생성자를 먼저 생성하고 setUrl을 하기 때문에
생성자 생성 시점에는 url이 모두 null이 됩니다.
즉 , url정보 없이 connect가 호출됩니다.
정리해보면 스프링 빈은 "객체 생성 -> 의존관계 주입"의 라이프 사이클을 가집니다.
스프링 빈 라이프 사이클
스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에 필요한 데이터를 사용할 수 있는 준비가 완료됩니다.
따라서 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 합니다.
하지만 개발자 입장에서는 의존관계가 모두 완료된 시점을 알기 어렵습니다.
때문에 스프링 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점을 알려주는 기능이 제공됩니다.
더불어 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 줘, 안전하게 종료 작업을 진행할 수 있습니다 .
스프링 빈의 라이프 사이클
"스프링 컨테이너 생성" -> "스프링 빈 생성" -> "의존관계 주입" -> "초기화 콜백" -> "사용" -> "소멸전 콜백" ->"스프링 종료"
-초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
-소멸전 콜백: 빈이 소멸되기 직전에 호출
\
참고) 객체의 생성과 초기화를 분리하자!
-생성자는 필수 정보(파라미터)를 받고 메모리를 할당해서 객체를 생성하는 책임을 가집니다.
-초기화는 생성된 값들을 활용해서 외부 커넥션을 연결하는 동작을 수행합니다.
->따라서 생성자 안에서 초기화 작업을 함께하는 것 보다 생성/초기화 부분을 나누는 것이 유지보수 관점에서 좋다.
이와 같은 라이프 사이클을 거칩니다,.
->스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원합니다.
1.인터페이스(InitializingBean, DisposableBean)
2.설정 정보에 초기화 메서드, 종료 메서드 지정
3. @PostConstruct, @PreDestroy 애노테이션 지원
1.인터페이스 (InitializingBean, DisposableBean)
클래스에 implements InitializingBean, DisposableBean을 추가해주고
afterPropertiesSet()과 destroy() 메서드를 추가해준다.
이는 각각 InitializingBean, DisposableBean에서 지원하는 메서드로,
afterPropertiesSet()은 빈에 의존관계가 주입이 완료되면 호출되고
destroy()는 빈이 소멸되기 전에 호출된다.
이를 추가한 후 테스트를 실행해보면
위와 같이, 생성자 호출 시점에는 url이 null이지만
주입이 끝난 후 afterPropertiesSet이 호출되어 주입이 완료되었음을 콜백해주고
connect/ call 메서드 실행시 url이 제대로 set되어 있음을 확인할 수 있다.
이후 다시 ac.close()가 호출되면, 컨테이너가 종료된다는 의미이므로
Closing NetworkClient.destroy를 보면 알 수 있듯, destroy 메서드가 호출되고
disConnect()메서드가 호출되어 , 소멸됨을 볼 수 있다.
이때 어떤 걸 기준으로 getBean을 하면 의존관계 주입가 완료되었다고 판단하고,
afterProperties를 호출하는지 궁금했는데
컨테이너의 빈에 등록된 정보에서 key에 따른 value의 값이 null이 찍혀있는것이 없으면
의존관계 주입이 끝났다고 판단을하고, afterProperties를 호출한다고 한다.
또한 getBean()과 ac.close() 메서드가 각각 생성/초기화 , 소멸 메서드임을 스프링이 알기 때문에
getBean()을 실행 시, destroy() 메서드가 소멸 메서드인 것을 알고 실행시키지 않는다고 한다.
반대로 ac.close()메서드가 실행되면 DisposableBean에 의해 관리되는 destroy()메서드가
빈을 소멸시키는 메서드임을 알고, 자동으로 불러낸다고 한다.
<정리>
->InitializingBean은 afterProperties() 메서드를 지원하고 이는 빈의 의존관계 주입이 끝나면 호출됨.
->DisposableBean은 destroy()메서드를 지원하고 이는 빈의 소멸 전 단계에서 호출됨.
<초기화/ 소멸 인터페이스 단점>
-> 스프링 전용 인터페이스 이므로, 스프링에 의존하는 코드
->초기화, 소멸 메서드의 이름을 변경할 수 없다.
-> 내가 코드를 고칠 수 없는 외부 라이브러리에는 적용할 수 없다.
-이러한 단점을 때문에 최근에는 많이 사용하지 않는 방법입니다.
2. 설정 정보에 추가
이번엔 이와 같이 @Bean에 (initMethod , destroyMethod)를 사용하여 초기화, 소멸 메서드를 지정했습니다.
그리고는 NetworkClient 클래스에 위 두개의 메서드를 추가한 후 테스트를 진행했습니다.
생성자 생성시에는 url이 null로 세팅되어있다가
initMethod로 설정한 init() 메서드가 실행되어 NetworkClient.init이 실행되어 의존관계를 주입해주고,
반대로, destroyMethod로 지정한 close()메서드가 소멸 단계에서 사용됩니다.
즉, initMethod로 설정하면, 스프링에서는 "아 init 이름의 클래스가 의존관계 주입이구나" 하고 알고
의존관계 주입단계에서 init() 메서드를 실행하고, 반대의 상황에서도 close() 메서드가 실행됩니다.
인터페이스 방법과 비교했을 때의 특징
-메서드 이름을 자유롭게 줄 수 있다.
- 스프링 빈이나 스프링 코드에 의존하지 않는다.
-코드가 아닌 설정 정보를 사용하므로 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.
추가) destroyMethod 속성에는 "추론"이라는 기능이 있는데
대부분의 라이브러리에서는 close, shutdown이라는 이름의 종료 메서드를 사용합니다.
이를 알고, destroyMethod의 기본값이 (inferred) (추론)으로 등록되어 있는데
이 기능은 close, shutdown 이라는 메서드를 자동으로 호출하게 만듭니다.
3.애노테이션 @PostConstruct @PreDestroy
그 어느 방법보다 간단하고 깔끔한 방법입니다.
스프링에서도 최근 가장 권장하는 방법이기도 하고, 애노테이션 하나만 붙이면 되기 때문에 편리합니다.
더군다나 javax.annotation.PostConstruct를 보면 스프링에 종속적인 기술도 아님을 알 수 있습니다.
때문에 스프링이 아닌 다른 컨테이너에서도 잘 동작합니다.
유일한 단점이 있는데, 외부 라이브러리에는 적용이 안되므로 이때는
@Bean(initMethod="" , destroyMethod="")를 활용하면 됩니다.
'스프링' 카테고리의 다른 글
스프링 기초 - 빈 스코프 (1) | 2024.02.21 |
---|---|
스프링 기초 - 의존관계 주입 (0) | 2024.02.15 |
스프링 기초- 컴포넌트 스캔 (0) | 2024.02.15 |
스프링 기초 - 싱글톤 컨테이너 (0) | 2024.02.14 |
스프링 기초 - 스프링 컨테이너 (0) | 2024.02.14 |