본문 바로가기
Study/Java Spring Boot

[Spring] 핵심 원리 8 빈 생명주기 콜백

본 글은 김영한님의 강의 내용을 바탕으로 정리한 글입니다.

 

스프링 핵심 원리 - 기본편 - 인프런 | 학습 페이지

지식을 나누면 반드시 나에게 돌아옵니다. 인프런을 통해 나의 지식에 가치를 부여하세요....

www.inflearn.com

목차

  1. 빈 생명주기 콜백 시작
  2. 인터페이스 InitializingBean, DisposableBean
  3. 빈 등록 초기화, 소멸 메서드 지정
  4. 애노테이션 @PostConstruct, @PreDestroy

 

1. 빈 생명주기 콜백 시작

모든 객체에는 생명주기가 존재한다.  

데이터베이스 Connection 풀이나 Network Socket 등의 객체의 생명주기를 관리해보자.

Spring에서 제공하는 초기화와, 종료작업까지를 예제로 배워본다.

 

아래와 같이 NetworkClient 클래스를 Test밑에 만들어보자 (실제 외부 서버와 연결은 안함)

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 msg){
        System.out.println("call : " + url);
        System.out.println("call msg = " + msg);
    }
    public void disconnect(){
        System.out.println("close : " + url);
    }

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

import static org.junit.jupiter.api.Assertions.*;

public class BeanLifeCycleTest {
    @Test
    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을 넣어주도록 설계된 프로그래밍이다.

위와 같은 구조의 소켓 클라이언트를 만들어야 할때가 종종 있는데,

실행해보면  url값이 들어오기도 전에 생성후 connect, call까지 실행이 되버렸다.

스프링에서는 객체생성 → 의존관계 주입 순서로 실행사이클을 가지므로,

이러한 작업이 끝나고나서 초기화 작업 (Connect , call) 이 이루어져야 하겠다.

 

그래서 스프링에서 빈 객체의 이벤트 사이클은 

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

와 같은 순서로 진행이 된다.

 

의존관계 주입이 끝나는 시점에 초기화 콜백은 크게 3가지 방법으로 받을 수 있다.

  1. 인터페이스(InitializingBean, DisposableBean)
  2. 설정 정보에 초기화 메서드, 종료 메서드 지정
  3. @PostConstruct, @PreDestroy 애노테이션 지원

지금부터 하나씩 알아보자.

 

2. 인터페이스 InitializingBean, DisposableBean

 

InitializingBean, DisposableBean 두가지 인터페이스를 종종 사용한다.

InitializingBean 에서는 afterPropertiesSet() 를 통해서 값이 초기화 된 후 실행되는 콜백 메소드를 가지며,

DisposableBean 에서 destroy() 를 통해서, 빈 객체 소멸전 수행해주는 콜백 메소드를 지원한다.

아래와 같이 Implements하여 코드를 변경해보자.

package hello.core.lifecycle;

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

    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);
        System.out.println("call msg = " + msg);
    }
    public void disconnect(){
        System.out.println("close : " + url);
    }

    @Override
    public void destroy() throws Exception {
        disconnect();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        connect();
        call("초기화 연결 매세지");
    }
}

출력 결과를 보면,

빈 객체의 속성값들이 초기화된 이후에 수행되어졌음을 확인 할 수 있고,

빈 객체가 종료전에 Disconnect()까지 콜백 받아 메소드를 수행했음을 볼 수 있다.

 

 

이 방법은 간단 명료하지만,

메서드이름을 변경할 수 없고, 외부라이브러리에 적용할 수 없다.

방식 자체가 스프링 초창기 방식으로 현재 잘 사용하지 않으며 더 좋은 방법이 있다.

 

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

 

빈을 등록하는 시점에 @Bean(initMethod = "init", destroyMethod = "close") 처럼 작성하여

초기화, 소멸 메서드를 지정할 수 있다.

package hello.core.lifecycle;

public class NetworkClient {
    private String url;
    public NetworkClient() {
        System.out.println("생성자 호출, url = " + url);
    }
    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);
    }
    public void init() {
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메시지");
    }
    public void close() {
        System.out.println("NetworkClient.close");
        disConnect();
    }
}

init() , close() 를 만들어주고

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;

import static org.junit.jupiter.api.Assertions.*;

public class BeanLifeCycleTest {
    @Test
    void lifeCycleTest(){
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        NetworkClient client = ac.getBean(NetworkClient.class);
        ac.close(); //스프링 컨테이너를 종료, ConfigurableApplicationContext 필요
    }
    @Configuration
    static class LifeCycleConfig {
        @Bean(initMethod = "init", destroyMethod = "close") // 이렇게 지정
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        }
    }
}

@Bean 등록시 초기화 콜백, 소멸전 콜백을 지정 해주는 방식이다.

destroyMethod는 설정하지 않아도  (inffered) 추론 기능이 있어 

close , shoutdown 등의 종료 메소드가 있으면 찾아서 자동으로 호출해준다.

이 추론 기능을 막는 방법은 destroyMethod="" 와 같이 설정해주면, 기본적인 추론기능을 사용하지 않게 된다.

 

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

 

3가지 방법중에 자바표준을 따르는 방법으로

단순 @PostConstruct, @PreDestroy 어노테이션을 명시해준다. 

package hello.core.lifecycle;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class NetworkClient {
    private String url;
    public NetworkClient() {
        System.out.println("생성자 호출, url = " + url);
    }
    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);
    }
    @PostConstruct
    public void init() {
        System.out.println("NetworkClient.init");
        connect();
        call("초기화 연결 메시지");
    }
    @PreDestroy
    public void close() {
        System.out.println("NetworkClient.close");
        disConnect();
    }
}

  • 최신 스프링에서 가장 권장하는 방법이다.
  • 어노테이션 하나로 초기화 수행전 @PostConstruct 콜백메소드와  소멸전 @PreDestroy 콜백 메소드를 지정한다.
  • 어노테이션 명도 굉장히 직관적이다.
  • javax . annotation.  패키지에 있는 자바표준이다.
  • 컴포넌트 스캔과도 잘 어울린다.
  • 하지만, 외부라이브러리에는 적용할 수 없으므로, 이 경우 @Bean의 initMethod, DestroyMethod 방식을 사용하자

 

정리

  • 빈 생명주기에 생성전과 소멸전 콜백 메소드를 지정하는 방식은 3가지가 있다.
  • 그 중 @PostConstruct, @PreDestroy 애노테이션을 사용하자
  • 외부 라이브러리를 초기화, 종료해야 하면 @Bean 의 initMethod , destroyMethod 를 사용하자.

 

소스코드 ↓