jhhan의 블로그

스프링 코어(14) - 빈 생명주기 콜백 본문

Spring

스프링 코어(14) - 빈 생명주기 콜백

jhhan000 2021. 8. 16. 16:04

이번 포스트에서는 빈 생명주기에 대해 알아보겠습니다.

 

 

  • 애플리케이션의 시작 시점
  • 데이터베이스 연결, 외부 연결 등
  • 필요한 연결 관계를 미리 작업 - 초기화 작업

 

  • 애플리케이션의 종료 시점
  • 연결 관계들을 잘 종료(소멸)

 

애플리케이션의 시작과 종료에서는 위와 같은 설정이 필요합니다.

이런 설정을 스프링을 통해서 진행하는 것을 해볼 예정입니다.

 

 

단순하게 외부와 연결하는 어떤 모듈이 있다고 가정합니다.
(여기서는 그냥 String만 이용해서 이렇게 진행될 것이다.. 라고 해보겠습니다.)

위의 구조처럼 lifecycle 패키지 밑에 NetworkClient 클래스와 BeanLifeCycleTest 클래스를 만들어둡니다.

 

public class NetworkClient {

    /**
     * 2021.08.16
     * BeanLifeCycleTest를 위한 클래스
     */

    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("disconnect : " + url);
    }
}

NetworkClient 클래스입니다.

이런 식으로 연결이 된다고 생각해봅니다.
(실제로는 connect()와 disconnect()에 어떤 로직을 설계해야합니다..)

public class BeanLifeCycleTest {

    /**
     * 2021.08.16
     * BeanLifeCycleTest 실시
     */

    @Test
    public void lifeCycleTest() {
        ApplicationContext 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://spring-core.cycle");
            return networkClient;
        }
    }
}

BeanLifeCycleTest 클래스입니다.

이렇게 코드를 작성한다면 

  • ac.close(); 에서 에러가 납니다.

ApplicationContext는 close() 메서드를 지원하지 않기 때문입니다.

그래서 ConfigurableApplicationContext를 사용하겠습니다.

public class BeanLifeCycleTest {

    /**
     * 2021.08.16
     * BeanLifeCycleTest 실시
     */

    @Test
    public void lifeCycleTest() {
//        ApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);        : ApplicationContext는 close() 메소드를 제공하지 않음
        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://spring-core.cycle");
            return networkClient;
        }
    }
}

이렇게 작성합니다.

 

참고로

ConfigurableApplicationContext는 ApplicationContext를 상속하고,

AnnotationConfigApplicationContext는 ConfigurableApplicationContext를 상속해서

위처럼 코드를 작성해도 됩니다.

 

ApplicationContext ◀ ConfigurableApplicationContext ◀ AnnotationConfigApplicationContext 

이런 관계가 되겠네요.

 

그리고 실행을 해봅니다.

실행을 하면 아마도 이런 결과가 나올 것입니다.

  • 생성자를 호출할 때 : url이 null이고
  • connect()를 호출할 때 : url이 null이고
  • call()을 호출할 때 : url이 null이고,,

networkClient.setUrl("http://spring-core.cycle");  이건 실행되지 않았습니다.

 

아마 이런 이유를 잘 아실 것입니다.

networkClient가 생성자를 만들면서

  • connect() 호출
  • call() 호출
  • 그 후에 setUrl 호출

이런 순서가 되어서 url 값이 계속 null이 나오게 됩니다.

 

 

원래 스프링 빈은 다음과 같은 라이프 사이클을 갖는다고 합니다.

  1. 스프링 컨테이너 생성
  2. 스프링 빈 생성
  3. 의존관계 주입
  4. 초기화 콜백
  5. 사용
  6. 소멸 전 콜백
  7. 스프링 애플리케이션 종료

스프링 빈이 생성된 후 의존관계가 주입됩니다.

(예외적인 경우 : 생성자 주입은 스프링 빈이 생성될 때 의존관계가 주입됩니다.)

필드 주입과 Setter 주입은 위의 라이프 사이클처럼 동작합니다.

 

초기화 작업은 의존관계 주입까지 완료된 후에 진행이 된다고 합니다.

개발자는 의존관계 주입이 일어난 후 초기화를 진행해야 하는데, 의존관계 주입이 끝나는 시점을 어떻게 알 수 있을까요?

이러한 부분은 스프링의 콜백 메서드를 통해 알 수 있다고 합니다.

 

다시 한번 스프링 빈의 라이프 사이클을 생각해봅니다.

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

 

아, 이 라이프 사이클싱글톤으로 진행되는 빈에 적용되는 사항입니다.

 

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

  • 인터페이스(InitializingBean, DisposableBean)
  • 설정 정보에 직접 추가
  • 애노테이션(@PostConstruct, @PreDestroy)

 

 

참고.

객체의 생성과 초기화는 분리하는 것이 유지보수 측면에서 유리하다고 합니다.

  • 생성자는 파라미터를 받아서 객체를 생성하는 역할
  • 초기화는 생성된 값을 사용해서 외부 연결을 하는 등의 역할 수행
  • 이 때 초기화 작업은 무거운 작업 위주로 진행된다고 합니다.
  • 이런 초기화 작업을 생성자에서 같이 진행하는 것은 좋지 않다라고 볼 수 있습니다.
  • 물론 간단한 초기화는 생성자 내에서 처리해도 됩니다.

 

 

 

이번 포스트는 여기서 마치고

다음 포스트에서는 3가지 지원방식에 대해서 알아보겠습니다.

 

 

 

 

 

출처: 인프런 - 스프링 핵심원리(기본편) by 김영한