빈생명주기 콜백

고동현·2024년 4월 8일
0

Spring 기본

목록 보기
8/10
post-thumbnail

데이터베이스 커넥션 풀이나, 네트워크 소켓처럼

애플리케이션 시작 시점에 미리 필요한 연결을 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면,

객체의 초기화와 종료 작업이 필요하다.

자 예시를 들어보자, 간단하게 외부 네트워크에 미리 연결하는 개체를 생성한다고 가정해 보자,

미리 짚고 넘어가야하는 부분은, 객체의 생성과 초기화는 전혀 다른 부분이다.

  • 객체의 생성은 생성자를 통해 필수정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는것이다.
  • 초기화는 이렇게 생성된 객체를 활용해서 외부 커넥션을 연결하는 무거운 동작을 수행하는것이다.

그러면 여기서 질문을 할 수있다. 미리 커넥션을 연결한 url도 파라미터로 받아서 그냥 객체를 생성할때, 연결까지 같이 하면 되는거잖아?

그런데 객체를 생성하는 부분과 초기화 하는 부분을 명확하게 나누는것이 유지보수관점에서 좋다.

물론, 초기화 작업이 내부값들만 변경하는 그정도로 단순한경우에는 생성자에서 처리하는것이 더 나을 수 있지만,
이렇게 자원을 많이 쓰는 작업은, 초기화단계에서 처리하는것이 좋다.

또 이렇게 하면 장점이, 필요한 객체를 미리 만들어서 가지고 있다가, 실제 외부커넥션을 맺는것은 최초의 어떤 행위가 요청될때 까지 미룰 수 있다.
그 다음에 어떤 행위가 호출되면 그때 미리 만들어 놓은 객체를 초기화를 통해 사용하면 좋은점들이 있다.

이러한 방식을 지연로딩, 느린초기화라고 하는데

  1. 애플리케이션 시작 시간 단축: 애플리케이션 시작시 모든 외부 연결을 초기화 하는대신 필요할때까지 초기화를 미루면, 애플리케이션의 시작 시간을 크게 단축할 수 있다.

  2. 자원 사용 최적화: 모든 연결을 미리 초기화 하지 않고 필요할 때만 초기화하면, 사용하지 않는 연결에 대한 자원을 절약할 수 있다.

  3. 가용성 향상: 초기화 과정에서 실패 할 수 있는 외부 시스템에 대한 의존도를 줄여, 애플리케이션의 전반적인 가용성을 향상시킬 수 있습니다.
    필요한 순간까지 연결을 미루면, 일시적인 외부 시스템의 장애가 애플리케이션의 가용성에 미치는 영향을 최소화 할 수 있다.

이러한 배경지식을 가지고 일단 코드를 봐보도록 하자.
실제 네트워크에 연결하는것은 아니고, 단순히 문자열만 출력하는것이다.


여기서 보면, 네트워크를 연결=>초기화하는 과정은 setUrl을 불러야 외부 커넥션이 연결이 되고

그냥 NetworkClinet를 생성하면 그냥 외부 커넥션 없이 객체가 생성만된다.

이렇게 하는이유는 앞에서 말했듯이 지연로딩으로 객체를 일단 만들고, 실제 사용이 되야할때 setUrl 메서드를 불러서 커넥션을 맺고 사용하려는 것이다.

TestCode

결과
생성자 호출, url = null
connect: null
call: null message = 초기화 연결 메시지

왜? setUrl을 해줘도 결과 로그에는 null이 뜰까? 당연하다.
왜냐하면 생성자에서는 setUrl을 해주지 않았으니까 말이다.
나중에 setUrl로 커넥션을 맺고 이걸 빈으로 등록해준 것이다.

스프링 빈은 이전에도 계속 말했지만 객체생성,의존관계주입 이렇게 두가지로 라이프 사이클이 나뉜다.

스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에 필요한 데이터를 사용할 수 있는 준비가 완료된다.

따라서 당연히 초기화작업=>실제 객체를 쓰기위해서 네트워크 연결이라던지, 데이터 베이스 커넥션 풀처럼, 데이터베이스와 연결을 맺고 해제하는 과정은 시간과 자원이 매우 소모되서,
미리 애플리케이션 시작시 커넥션 풀은 설정에 따라 정해진 수의 데이터 베이스 연결을 생성하고 풀에 저장하고,=>이러면 데이터베이스 연결을 생성한거지 아직 연결이 된상태는 아닌거다. set메서드처럼 set이 안된상태이다.

그 다음에 애플리케이션이 데이터베이스 연결을 요청하게 되면, 그때 커넥션 풀에 있는 하나를 가져와서 set을 통해서 연결을 하고 그다음에 끝나면 풀로 반환하는 이런 것이다.

[저도 자세한 깊은 내용은 모릅니다.. 그냥 초기화와 생성이 어떻게 구분되는지 정도 이해했습니다.]

스프링 빈의 이벤트 라이프 사이클
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료

  • 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입 완료후 호출
  • 소멸전 콜백: 빈이 소멸되기 직전에 호출

만약 초기화 콜백이 필요없이 그냥 빈생성 후에 바로 이용이 가능하다면?
그럼 그냥 뛰어넘고 사용하면 됨

참고로, 싱글톤 빈들은 당연히 컨테이너가 관리하므로, 스프리이 컨테이너가 종료될때 싱글톤 빈들도 함께 종료되기 때문에 스프링 컨테이너가 종료되기 직전에 소멸전 콜백이 일어난다.

뒤에서 설명하겠지만, 싱글톤 처럼 컨테이너의 시작과 종료까지 생존하는 빈들도 있지만, 생명주기가 짧은 빈들도 있는데 이빈들은 컨테이너와 무관하게 해당 빈이 종료되기 직전에 먼저
소멸전 콜백이 일어난다.
=> 자세한 내용은 스코프에서 설명

스프링은 크게 3가지 방식으로 빈 생명주기 콜백을 지원한다.

  • 인터페이스
  • 설정정보에 초기화 메서드, 종료 메서드 지정
  • @PostConstruct, @PreDestroy 어노테이션 지원

인터페이스 - InitializingBean,DisposableBean



initializationBean이랑 DisposableBean인터페이스를 활용하는 방식이다.

이렇게되면 자동적으로 의존관계 주입까지 끝나면 스프링이 afterPropertiesSet을 호출해서 초기화를 해주고
종료할때 destroy메서드를 호출해서 disConnect메서드를 호출한다.

출력결과
생성자 호출, url = null
NetworkClient.afterPropertiesSet
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
13:24:49.043 [main] DEBUG
Closing NetworkClient.destroy
org.springframework.context.annotation.AnnotationConfigApplicationContext -
close + http://hello-spring.dev

당연히 처음 생성자를 호출할때는 url set을 안해줬으니까 null이고
afterPropertiesSet메서드가 호출되면 그때 connect메서드와 call메서드가 호출되면서 연결후 close시에 destory메서드가 호출되는형태이다.

  • 초기화, 소멸 인터페이스의 단점
    이 인터페이스는 스프링 전용 인터페이스 이다. 해당 코드가 스프링 전용 인터페이스에 의존한다.
    초기화,소멸메서드의 이름을 변경할 수 없다.
    내가 코드를 코칠 수 없는 외부 라이브러리에 적용 할 수 없다.

그래서 인터페이스 방식은 지금 더 나은방식이 있기에 사용되지 않는다.

빈 등록 초기화, 소멸 메서드 지정

설정정보에 @Bean(initMethod = "init", destroyMethod = "close")처럼 초기화, 소멸 메서드를 지정가능하다.


앞의 initializationBean처럼 메서드에다가 call과 connect메서드를 부르는데, afterPropertiesSet처럼 지정된 메서드 명이 아니라 지신이 원하는 메서드 명으로 만든다.


사용할때는 bean에다가 init과 destroyMethod를 선언해주면,
Bean에 등록될때 의존관계 주입 다끝나고,
init메서드를 불러서 connect와 call을하고
종료될때 destroyMethod로 close를 한다.

다시 말하자면, 어? 왜 어차피 빈등록 하고 그다음 초기화 콜백을 사용할바에는 그냥 빈등록하면서 초기화 콜백까지 하는게 낫지 않나?

여기서는 순차적으로 빈등록=>초기화 콜백하였지만
나중에는 빈등록 => 필요할때까지 지연시키다가 => 요청 들어오면 초기화 콜백
이렇게 사용하기 위해서 알아봄

  • 메서드 이름을 자유롭게 줄수 있다.
  • 스프링 빈이 스프링 코드에 의존하지 않는다. NetworkClient에는 어떠한 스프링 기능도 들어가 있지 않다.
  • 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용 가능하다.

참고: Bean destroy의 기능
라이브러리는 대부분 close,shutdown이라는 이름의 종료 메서드를 사용한다.
@Bean의 destroyMethod는 기본값이 (inferred)으로 등록 되어있다.

고로 여기서 자동적으로, close,shutdown이라는 이름의 메서드를 자동적으로 호출해준다.
따라서 스프링 빈으로 직접 등록하면 종료메서드를 따로 적어주지 않아도 잘 작동한다.

@PostConstruct, @PreDestroy

결국 이걸 쓰면된다.

일단 이걸 애노테이션을 써야하는 이유가 편리하다.
패키지를 보면 javax.annotation.PostConstruct이다.
즉 스프링에 종속적인 기술이 아닌 JSR-250 이라는 자바 표준이다.

그러니까 한마디로 꼭 이 애노테이션을 사용한 코드는 스프링뿐만 아니라, 자바 EE 기술을 지원하는 다른 컨테이너 환경에서도 동작한다는 말이다.
예를 들어, Java EE 서버인 WildFly나 GlassFish 등에서도 @PostConstruct와 @PreDestroy 애노테이션이 정상적으로 작동한다.

그러나 단점으로는 외부 라이브러리에 적용하지 못한다. 왜냐하면, 내가 외부 라이브러리 코드를 까서 여기서 @PostConstruct, @PreDestroy를 적을수는 없기 때문이다.

그래서 코드를 고칠 수 없는 외부 라이브러리에서 초기화, 종료가 필요하다면 @Bean의 initMethod,destroyMethod를 사용해야한다.

profile
항상 Why?[왜썻는지] What?[이를 통해 무엇을 얻었는지 생각하겠습니다.]

0개의 댓글