빈 생명주기 콜백

2022. 10. 26. 16:31자바 & Spring

스프링 컨테이너의 역할

  • 자동으로 스프링 Bean 객체에 의존성을 주입.
  • 객체의 생성부터 소멸까지 생명주기(lifecycle)를 관리.

이렇게 객체 관리의 주체가 스프링이 되면, 개발자는 핵심 로직에만 집중할 수 있게 된다.

 

빈 생명주기 콜백의 필요성

스프링 빈은 초기화 작업, 종료 작업을 나눠 진행한다.

어플리케이션이 시작 될 때 수행하려는 작업과, 어플리케이션이 종료되는 시점에서 수행하려는 작업이 다르기 때문이다.

둘을 구분하는 것이 성능 면에서도 좋다.

 

 

스프링 빈 라이프사이클

  • 객체 생성 → 의존관계 주입

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

 

객체의 생성과 초기화 분리

생성자 안에서 초기화 작업을 함께하는 것보다, 객체를 생성하는 부분과 초기화하는 부분을 명확하게 나누는 것이 유지보수 관점에서 좋다고 한다.

 

생성자가 파라미터를 받아 객체를 생성하고, 초기화는 객체 필드의 값들을 초기화한다.

초기화하는 것들 중에 시간이 오래걸리거나, 무거운 일들도 있을 수 있다.

 

빈 객체가 의존관계까지 모두 완료가 된 다음에야 사용할 준비가 되었다고 했는데 그렇다면 개발자가 그 시점을 어떻게 알 수 있을까?

 

 

의존관계 주입이 모두 완료된 시점을 어떻게 알 수 있을까?

  • 의존관계 주입이 완료되면 스프링은 스프링 빈의 콜백 메서드를 통해 초기화 시점을 알려준다.
  • DI(의존성 주입) 완료된 후 스프링이 초기화 콜백을 주어 객체가 사용할 준비가 되었음을 알린다.
  • 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다. 따라서 안전하게 종료 작업을 진행할 수 있다.

 

콜백(Call back) 이란?

주로 콜백함수를 부를 때 사용되는 용어이다. 피 호출자(Callee) 가 호출자(Caller)를 다시 호출하는 것.

콜백 함수란 다른 함수에 인수로 전달되는 함수이며, 특정 이벤트 발생 후 실행되는 것이다.

콜백 함수의 용도는 다른 클래스에서 일부 작업이 완료된 경우 클래스에 완료되었다는 것을 알려주기 위함이다.

 

스프링 빈의 이벤트 라이프사이클

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

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

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

  • 인터페이스(InitializingBean, DisposableBean)
  • 설정 정보에 초기화 메서드, 종료 메서드 지정
  • @PostConstruct, @PreDestroy 지원

 


1. 인터페이스를 이용한 콜백

인터페이스를 이용한 초기화, 종료 방법을 살펴보자.

 

예시

    @Test
    public void TestLifeCycle(){
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
        WebClient client = ac.getBean(WebClient.class);
        ac.close();
    }

    @Configuration
    static class Config{
        @Bean
        public WebClient webClient(){
            WebClient webClient = new WebClient();
            webClient.setTemp("클라이언트A 접속--");
            return webClient;
        }
    }
public class WebClient implements InitializingBean, DisposableBean {
    private String temp;

    public NetworkClient() {
    	System.out.println("클라이언트 생성됨 = " + temp); 
    }
    
    public void setTemp(String temp) {
    	this.temp = temp;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    	// connect();
        call("초기화 완료");
    }

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

 

 

초기화 메서드 afterPropertiesSet 이 의존관계 주입 완료 후에 적절하게 호출된다.

  • InitilaizingBean 인터페이스 오바라이딩

스프링 컨테이너의 종료가 호출되자 소멸 메서드 destroy가 호출된다.

  • DisposableBean 인터페이스 오버라이딩

InitialiazingBean, DisposableBean 인터페이스를 상속받아 두 메서드를 오버라이딩했다.

해당 메소드가 있는 클래스의 빈 객체가 초기화가 되거나 소멸하기 직전에 각각 호출된다.

 

 

단점

  • 스프링 전용 인터페이스로, 스프링 프레임워크에 의존적이다.
  • 초기화, 소멸 메서드의 이름 변경 불가능.
  • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.

 

왜 외부 라이브러리에 적용할 수 없다는 것일까?

외부 라이브러리 코드는 내가 마음대로 수정할 수 없다. InitializingBean, DisposableBean 인터페이스를 외부 라이브러리에 코드에 implements 할 수 없다.

위 두개의 인터페이스를 적용할 수 없으니 오버라이딩 메서드를 구현할 수 없다.

afterPropertiesSet, destroy 메서드가 구현되지 않으니 스프링이 로드될 때 두 메서드가 호출되지 않으므로,

외부 라이브러리에 적용할 수 없다는 뜻이다.

 

 

위 예시에서 ConfigurableApplicationContext 를 사용한 이유는 무엇일까?

ConfigurableApplicationContext 인터페이스

ApplicationContext, LifeCycle, Closable 을 상속받고 있다.

위 테스트 예시에서 ac.close()는 Closable 인터페이스에서 나온 것이다.

ac.close()를 호출하면 스프링 컨테이너를 종료하게 된다. 그에 따라 컨테이너에 등록되었던 스프링 빈도 모두 지워지게 된다.

ApplicationContext에는 close() 기능이 없다. → Closable 상속X

소멸 콜백 메서드를 실험하기 위해 스프링 컨테이너를 종료하기 위해 close()를 쓰기위해 ConfigurableApplicationContext 구현체를 사용한 것이다.

 

 


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

설정 정보(Config class)에

@Bean(initMethod = "init", destroyMethod = "close") 처럼 초기화, 소멸 메서드를지정할 수 있다.

@Bean 의 필드값에 내가 지정한 함수의 이름을 쓰면 초기화, 소멸 메서드로 동작하게 된다.

다음과 같이 설정하면 NetworkClient 빈에서 초기화 콜백으로 init 메서드, 소멸전 콜백으로 close 메서드를 호출한다.

@Configuration
static class CycleConfig {
    @Bean(initMethod = "init", destroyMethod = "close")
    public WebClient webClient() {
        WebClient webClient = new WebClient();
        webClient.setTemp("AAA");
        return webClient;
    }
}

 

@Bean 필드 값으로 지정된 init, close 메서드이다.

public void init() {
   System.out.println("webClient 초기화");
    // 구현 로직
}

public void close(){
	System.out.println("WebClient 종료");
	// 구현 로직
}

init 은 webClient 빈의 초기화 콜백 메서드로서 작용하고,

close는 webClient 빈의 소멸전 콜백 메서드로 작용한다.

 

설정 정보 사용 특징

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

 

추가내용

아까 InitializingBean, DisposableBean은 외부 라이브러리에 적용할 수 없다고 했다.

그렇다면 이번에는 왜 외부 라이브러리에 적용할 수 있다는 것일까?

 

외부 라이브러리의 형태

외부 라이브러리는 보통 class 파일로 구성되어 있다. 이는 사용자에게 배포할 때 java 파일이 아닌 컴파일 뒤 생성된 결과물인 class 파일을 .jar 파일로 패키징하여 배포한다.

class 파일은 바이트코드로 구성되어 있어 JVM이 읽기 편하지만 개발자가 읽고 수정하기는 어렵다.(불가능한 것은 아니라고 함.)

 

 

어느 외부 라이브러리에 NetWorkClient 클래스가 존재한다고 하자.

  • 이 클래스 안에 aInit, aDestroy 라는 메서드가 있다.
  • 우리는 NetWorkClinet 클래스를 스프링 빈으로 수동 등록하고, 스프링이 로딩되고 종료될 때 자동으로 ainit, aDestroy 메서드를 실행되게 만들려고 한다.

 

1) 처음 나왔던 InitializingBean, DisposableBean 인터페이스를 사용

위 2가지 인터페이스를 NetWorkClent에 적용하고, 오버라이딩 메서드 2개: afterPropertiesSet, destroy를 클래스 안에 구현해야 한다. 하지만 외부 라이브러리는 클래스 파일이므로 코드를 변경하기 어렵다.

따라서 생성, 소멸 콜백 메서드 호출 불가능.

 

2) 설정 정보 사용

NetWorkClient 코드를 직접 수정하는 대신, 개발자가 @Configuration 클래스를 만들어 @Bean 속성으로 initMethod, destroyMethod 를 사용할 수 있다.

 


3. @PostConstruct, @PreDestroy

@PostConstruct, @PreDestroy 두 어노테이션으로 가장 편리하게 초기화와 종료를 실행할 수 있다.

현재 스프링에서 가장 권장하는 방법으로, 대부분의 실무에서 사용하는 방법이라고 한다.

 

특징

  • 최신 스프링에서 가장 권장하는 방법이다. 어노테이션만 붙이면 되므로 매우 편리하다.
  • 패키지가 javax.annotation.PostConstruct 이다. 자바 표준 기술로, 스프링에 종속적인 기술이 아니다. 즉 다른 프레임워크에서도 얼마든지 동작가능.
  • 컴포넌트 스캔과 잘 어울린다.

단점

  • 유일한 단점은 외부 라이브러리에 적용하지 못한다는 것. ⇒ 직접 코드 수정을 못하기 떄문에.
  • 외부 라이브러리를 초기화하고, 종료해야 하면 설정 파일의 빈에 콜백 메서드를 등록하자.

'자바 & Spring' 카테고리의 다른 글

JPA 정의  (0) 2022.11.06
JDBC(Java Database Connectivity)  (0) 2022.11.02
컴포넌트 스캔, 의존관계 자동 주입  (2) 2022.09.25
싱글톤(singleton) 패턴  (0) 2022.09.24
Spring 특징과 장점  (0) 2022.09.17