김영한 스프링 핵심 원리(기본편) - 빈 생명주기 콜백

개발할래·5일 전
0

개발

목록 보기
18/19
post-thumbnail

스프링 빈 생명주기(Spring Bean LifeCycle)

1. 빈 생명주기 콜백 시작

스프링을 통해 이러한 초기화 작업과 종료 작업이 어떻게 진행되는지 예제로 확인

  • NetworkClient.class
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);
    }

    // 연결이 된 상태에서 call을 호퉁해서 연결한 서버에 메시지를 던질 수 있다고 가정
    public void call(String message){
        System.out.println("call: " + url + " message: " + message);
    }

    // 서비스 종료시 호출
    public void disconnect(){
        System.out.println("close : " + url);
    }
}
  • BeanLifeCycleTest.class
package hello.core.lifecycle;

import org.junit.jupiter.api.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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");

            return networkClient;
        }
    }
}

  • 생성자 부분을 보면 url 정보 없이 connect가 호출되는 것이 확인
    객체를 생성한 다음에 외부에서 수정자 주입을 통해서 setUrl() 호출 시 url이 존재

스프링 빈 라이프사이클

  • 객체 생성 -> 의존관계 주입
    ("생성자 주입" 예외, 생성자 주입은 객체를 생성할 대 스프링 빈이 파라미터로 같이 들어와야 함)

    • 스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에 필요한 데이터를 사용할 준비가 완료
      : 1) 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점을 알려주는 다양한 기능을 제공 2) 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 전달(빈이 종료되기 직전에 소멸 콜백을 받음)

스프링 빈의 이벤트 라이프사이클 (참고. 싱글톤에 대한 예시)
: 1)스프링 컨테이너 생성
2)스프링 빈 생성
3)의존관계 주입(setter 주입, 필드 주입)
4)초기화 콜백
5)사용
6)소멸 전 콜백
7)스프링 종료

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

참고

  • 객체의 생성과 초기화 분리
    객체의 생성은 필요한 필수 값들만 정의해서 객체가 생성되는데에만 집중하고 = 메모리 할당에 초점
    이후 객체가 기능을 하기위해 동작을 위한 초기화 작업 분리 (단일 책임의 관점)
  • 생성자는 필수 정보를 받아 객체를 생성하고 메모리를 할당함
    초기화는 생성된 값을 활용해 외부 커넥션 등 무거운 작업을 수행함
    따라서 생성자와 초기화 작업을 분리하는 게 유지보수에 좋음
    단, 초기화가 간단한 경우엔 생성자에서 처리하는 게 나을 수 있음

  • 싱글톤 빈들은 스프링 컨테이너가 종료될 때 싱글톤 빈들도 함께 종료 -> 스프링 컨테이너가 종료되기 직전에 소멸전 콜백이 발생

스프링 빈 생명주기 콜백 지원

2. 인터페이스(InitializingBean, DisposableBean)

: 인터페이스로 초기화와 소멸전 콜백을 받는 방법

  • NetworkClient.class
package hello.core.lifecycle;


import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class NetworkClient implements InitializingBean, DisposableBean {

    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);
    }

    // 연결이 된 상태에서 call을 호퉁해서 연결한 서버에 메시지를 던질 수 있다고 가정
    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();
    }
}
  • InitializingBean은 afterPropertiesSet() 메서드로 초기화를 지원

  • DisposableBean은 detroy() 메서드로 소멸을 지원

  • BeanLifeCycleTest 클래스의 lifeCycleTest 테스트

    • 생성자 호출 단계 : url 정보는 없이 출력, 빈 생성이 끝나고 의존관계 주입이 끝난 다음 afterPropertiesSet가 호출 되면서 connect와 call이 호출, 다음 스프링 컨테이너가 내려가서 종료되기 전에 destroy 호출 및 disconnect 호출
    • 출력 결과를 보면 초기화 메서드가 주입 완료 후에 적절하게 호출
    • 스프링 컨테이너의 종료가 호출되자 소멸 메서드가 호출
  • 초기화, 소멸 인터페이스 단점

    • 인터페이스는 스프링 전용 인터페이스, 스프링 전용 인터페이스에 의존
    • 초기화, 소멸 메서드의 이름을 변경할 수 없음
    • 외부라이브러리에 적용할 수 없음

3. 빈 등록 초기화, 소멸 메서드

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

  • NetworkClient 클래스
package hello.core.lifecycle;


import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

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);
    }

    // 연결이 된 상태에서 call을 호퉁해서 연결한 서버에 메시지를 던질 수 있다고 가정
    public void call(String message){
        System.out.println("call: " + url + " message: " + message);
    }

    // 서비스 종료시 호출
    public void disconnect(){
        System.out.println("close : " + url);
    }

    public void init() throws Exception{
        System.out.println("NetworkClient init");
        connect();
        call("초기화 연결 메시지");
    }

    public void close() throws Exception{
        System.out.println("NetworkClient close");
        disconnect();
    }


}


  • 설정 정보 사용 특징
    • 메서드 이름을 자유롭게 설정
    • 스프링 빈이 스프링 코드에 의존하지 않음
    • 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드 적용
  • 종료 메서드
    • @Bean의 destroyMethod의 특별한 기능
    • 라이브러리는 대부분 close, shutdown 이라는 이름의 종료 메서를 사용
    • @Bean의 destroyMethod는 기본값이 (inferred) (추론)으로 등록
    • 해당 추론 기능은 close, shutdown 라는 이름의 메서드를 자동으로 호출, 이름 그대로 종료 메서드를 추론해서 호출
    • 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어지 않아도 동작을 잘함
    • 추론 기능을 사용하기 싫으면 destroyMethod="" 처럼 빈 공백을 지정

4. 애노테이션 @PostConstruct, @PreDestroy

  • NetworkClient @PostConstruct, @PreDestroy 적용
package hello.core.lifecycle;


import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

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);
    }

    // 연결이 된 상태에서 call을 호퉁해서 연결한 서버에 메시지를 던질 수 있다고 가정
    public void call(String message){
        System.out.println("call: " + url + " message: " + message);
    }

    // 서비스 종료시 호출
    public void disconnect(){
        System.out.println("close : " + url);
    }


    @PostConstruct
    public void init() throws Exception{
        System.out.println("NetworkClient init");
        connect();
        call("초기화 연결 메시지");
    }

    @PreDestroy
    public void close() throws Exception{
        System.out.println("NetworkClient close");
        disconnect();
    }


}
  • BeanLifeCycleTest 클래스에서 이전에 작성했던 초기화(initMethod), 소멸메서드(destroyMethod) 속성을 제거한 후 실행 테스트

    • @PostConstruct, @PreDestroy 이 두 애노테이션을 사용하면 편리하게 초기화와 종료를 실행
  • @PostConstruct와 @PreDestroy 애노테이션은 최신 스프링에서 권장하는 초기화 및 종료 방법임.

    • 편리함: 애노테이션 하나만 붙이면 되므로 사용이 간편함.
    • 표준: javax.annotation.PostConstruct에 정의되어 있어 스프링에 종속적이지 않음. JSR-250 자바 표준으로, 다른 컨테이너에서도 사용 가능.
    • 컴포넌트 스캔: 스프링의 컴포넌트 스캔과 잘 어울려 자동으로 초기화 및 종료 메서드를 호출함.
    • 단점: 외부 라이브러리에는 적용할 수 없음. 외부 라이브러리의 초기화나 종료가 필요할 경우 @Bean을 사용해야 함.

요약하자면, @PostConstruct와 @PreDestroy는 스프링에서 초기화와 종료를 간편하게 처리할 수 있는 표준 애노테이션이지만, 외부 라이브러리에는 적용할 수 없다는 점이 단점임

profile
내 인생부터 개발

0개의 댓글