본문으로 바로가기

[Spring] 스프링 빈 생명주기 콜백

category Java/Spring 2022. 1. 26. 14:07
반응형

 

 

 

스프링 빈의 이벤트 라이프사이클은 아래와 같다.

 

    1. 스프링 컨테이너 생성 

    2. 스프링 빈 생성

    3. 의존관계 주입

    4. 초기화 콜백

    5. 사용

    6. 소멸 전 콜백

    7. 스프링 종료

 

 

여기서 초기화 콜백은 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출되고,

소멸 전 콜백은 빈이 소멸되기 직전에 호출된다.

 

 

 

 

 

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

 

- 인터페이스(InitializingBean, DisposableBean)

- 설정 정보에 초기화 메서드, 종료 메서드 지정

- @PostConstruct, @PreDestroy 애노테이션 지원

 

 

 

 

 

하나씩 알아보자.

 

 

 

 

 


● 인터페이스(InitializingBean, DisposableBean)  

 

 

 

 

 

 

 

 

테스트 코드를 통해 알아보자.

 

 

 

 

 

 

EX) 간단하게 외부 네트워크에 미리 연결하는 객체를 하나 생성한다고 가정해보자.

     실제로 네트워크에 연결하는 것은 아니고, 단순히 문자만 출력하도록 했다.

     이 NetworkClient는 애플리케이션 시작 시점에 connect()를 호출해서 연결을 맺어두어야 하고,

     애플리케이션이 종료되면 disConnect() 를 호출해서 연결을 끊어야 한다

 

 

 

 

NetworkClient.java

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 message) {
 		System.out.println("call: " + url + " message = " + message);
 	}
 	
    //서비스 종료시 호출
 	public void disConnect() {
 		System.out.println("close + " + url);
 	}
 	
    	@Override
 	public void afterPropertiesSet() throws Exception {
 		connect();
 		call("초기화 연결 메시지");
 	}
    
 	@Override
 	public void destroy() throws Exception {
 		disConnect();
 	}
}

 

InitializingBean 은 afterPropertiesSet() 메서드로 초기화를 지원한다.

DisposableBean 은 destroy() 메서드로 소멸을 지원한다

 

 

 

 

 

이제 생성자와 소멸자 메소드를 호출하는 테스트 코드를 작성하자.

 

 

 

package hello.core.lifecycle;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
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();
    }

    @Configuration
    static class LifeCycleConfig {

       
        @Bean
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient(); // 객체를 먼저 생성(생성자 내부에 있는 메소드 등)
            networkClient.setUrl("http://hello-spring.dev"); // 객체를 먼저 생성한 다음 url을 주입했기에 내부(생성자)에서 실행한 것들은 null값이 나옴
            return networkClient;
        }
    }
}

 

 

 

 

 

테스트 실행 결과는

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

 

 

출력 결과를 보면

초기화 메서드가 주입 완료 후에 적절하게 호출된 것을 확인할 수 있다.

 

그리고 스프링 컨테이너의 종료가 호출되자 소멸 메서드가 호출된 것도 확인할 수 있다.

 

 

 

 

 

 

하지만 위에서 사용한 방법에는 몇가지 단점이 있다.

 

이 인터페이스는 스프링 전용 인터페이스여서

해당 코드가 스프링 전용 인터페이스에 의존한다.

 

또 초기화, 소멸 메서드의 이름을 변경할 수 없다.

 

추가로 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.

 

 

 

 

 

 

 

 


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

 

 

 

 

 

 

 

설정 정보에 @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();
    }
    
}

 

 

 

 

 

이제 작성했던 테스트 코드에서

@Bean에 아래와 같이 입력해준다.

 

@Configuration
static class LifeCycleConfig {

 @Bean(initMethod = "init", destroyMethod = "close")
 public NetworkClient networkClient() {
	NetworkClient networkClient = new NetworkClient();
 	networkClient.setUrl("http://hello-spring.dev");
 	return networkClient;
 }
}

 

 

 

테스트 결과는 위와 같이 정상적으로 작동한다.

 

 

 

 

 

설정 정보 사용 특징

 

- 메서드 이름을 자유롭게 줄 수 있다.

- 스프링 빈이 스프링 코드에 의존하지 않는다.

- 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도

  초기화, 종료 메서드를 적용할 수 있다.

 

 

 

 

 

 

 


● @PostConstruct, @PreDestroy 애노테이션 지원

 

 

 

 

 

 

 

이 방법은 매우 간단하다.

 

생성자, 소멸자 메서드에 @PostConstruct, @PreDestroy 을 선언해주면 된다.

 

 

코드를 살펴보자

 

 

 

package hello.core.lifecycle;


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 이 두 애노테이션을 사용하면

편리하게 초기화와 종료를 실행할 수 있다

 

 

 

 

 

 

@PostConstruct, @PreDestroy 패키지를 잘 보면 javax.annotation.PostConstruct이다.

스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다.

따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.

 

 

 

유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다.

외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 기능을 사용하자

 

 

 

 

 

 

반응형