본문 바로가기
카테고리 없음

(8) 빈 생명주기 콜백

by 샤샤샤샤 2023. 6. 5.

스프링 빈(싱글톤)의 생명 주기

스프링 컨테이너 생성 -> 스프링 빈 생성(생성자 함수 실행, 생성자 주입 실행) -> 의존관계 주입(생성자 주입을 제외한 의존관계 주입) -> 초기화 콜백 -> 사용 -> 소멸전 콜백 ->  스프링 종료

 

초기화 콜백, 소멸전 콜백은 스프링에서 자체적으로 지원하는 기능이다.

코드로 예시를 살펴보자.

 

 

#AutoConnect

public class AutoConnect {
    private String fieldA;

    public AutoConnect() {
        System.out.println("AutoConnect 객체 생성됨");
        System.out.println("field=" + fieldA);
        connect();
        call("초기화 연결 메시지");
    }

    public void setFieldA(String fieldA) {
        this.fieldA = fieldA;
    }

    // 서비스 시작시 호출
    public void connect(){
        System.out.println("connect:" + fieldA);
    }

    public void call(String message){
        System.out.println("CallField:" + fieldA +", message :" + message);
    }

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

#테스트 코드

public class BeanLifeCycleTest {

    @Test
    public void lifeCycleTest(){
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        AutoConnect client = ac.getBean(AutoConnect.class);
        ac.close();
    }

    @Configuration
    static class LifeCycleConfig{
        @Bean
        public AutoConnect autoConnect(){
            System.out.println("빈 등록함수 호출");
            AutoConnect autoConnect = new AutoConnect();
            System.out.println("객체 생성");
            autoConnect.setFieldA("aaaaaaaaaa");
            System.out.println("필드값 셋팅 완료");
            return autoConnect;
        }
    }
}

위의 코드처럼 객체를 먼저 생성하고 외부에서 필드값을 받거나 의존주입을 받는 경우, 생성자 함수가 사용자의 의도대로 동작하지 않는다.

객체 생성 -> set함수 순으로 작동한다.

스프링의 생명주기 콜백

스프링에서 지원하는 생명주기 콜백은 크게 3가지다.

1. 인터페이스 InitializingBean, DisposableBean

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

3. @PostConstruct, @PreDestroy 어노테이션 지원

 

1. 인터페이스

# AutoConnect


// 인터페이스 상속
public class AutoConnect implements InitializingBean, DisposableBean {
    private String fieldA;

    public AutoConnect() {
        System.out.println("AutoConnect 객체 생성됨");

    }

    public void setFieldA(String fieldA) {
        this.fieldA = fieldA;
    }

    // 서비스 시작시 호출
    public void connect(){
        System.out.println("connect:" + fieldA);
    }

    public void call(String message){
        System.out.println("CallField:" + fieldA +", message :" + message);
    }

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

// 오버라이딩한 메소드에 코드 입력

    @Override
    public void destroy() throws Exception {
        System.out.println("field=" + fieldA);
        connect();
        call("초기화 연결 메시지");
    }

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

이 방법을 사용하면 자동적으로 스프링이 객체 생성 + 초기화까지 완료된 다음 자동으로 afterPropertiesSet 내부 코드를 실행하는걸 볼 수 있다.

객체 생성 -> set함수 ->afterPropertiesSet 함수

 이 방법은 스프링 초창기에 나온 방법으로, 스프링 프레임워크에 의존한다는 단점과 메서드 이름 변경 불가, 외부 라이브러리 사용 불가의 단점이 존재해 지금은 쓰이지 않는 방법이다.

 

2. 설정 정보를 이용한 메서드 지정

@Configuration 클래스를 사용해 빈을 등록할 시, @Bean 의 옵션을 지정해 초기화, 소멸 메서드를 지정해줄수 있다.

 

@Bean(initMethod = "[초기화 메서드 이름]", destroyMethod = "[소멸 메서드 이름]")

이를 활용하면 지정된 메서드가 자동으로 실행된다.

이때 해당 메서드는 설정 파일이 아닌, 빈으로 등록될 클래스 내부에 있어야 한다.

 

# AutoConnect


public class AutoConnect  {
    private String fieldA;

    public AutoConnect() {
        System.out.println("AutoConnect 객체 생성됨");

    }

    public void setFieldA(String fieldA) {
        this.fieldA = fieldA;
    }

    // 서비스 시작시 호출
    public void connect(){
        System.out.println("connect:" + fieldA);
    }

    public void call(String message){
        System.out.println("CallField:" + fieldA +", message :" + message);
    }

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

    
    // Config에서 설정한 이름과 동일한 메서드
    public void init(){
        connect();
    }

    public void close(){
        disconnect();
    }

}

# BeanLifeCycleTest

public class BeanLifeCycleTest {

    @Test
    public void lifeCycleTest(){
        ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        AutoConnect client = ac.getBean(AutoConnect.class);
        ac.close();
    }

    @Configuration
    static class LifeCycleConfig{
        //빈 옵션 등록
        @Bean(initMethod = "init", destroyMethod = "close")
        public AutoConnect autoConnect(){
            System.out.println("빈 등록함수 호출");
            AutoConnect autoConnect = new AutoConnect();
            System.out.println("객체 생성");
            autoConnect.setFieldA("aaaaaaaaaa");
            System.out.println("필드값 셋팅 완료");
            return autoConnect;
        }
    }
}

결과:

이 방식은 메서드 이름을 자유롭게 설정할수 있으며 빈 클래스가 스프링에 의존하지 않는다. 또한 외부 라이브러리 역시 적용할수 있다.

*destroyMethod 의 경우, 따로 설정해주지 않으면 close나 shutdown이라는 이름의 메서드를 자동으로 호출해준다.

 

3. 어노테이션

config 클래스를 사용하는 방식이 간소화된 것이라고 생각하면 편하다.

 

# AutoConnect

public class AutoConnect  {
    private String fieldA;

    public AutoConnect() {
        System.out.println("AutoConnect 객체 생성됨");

    }

    public void setFieldA(String fieldA) {
        this.fieldA = fieldA;
    }

    // 서비스 시작시 호출
    public void connect(){
        System.out.println("connect:" + fieldA);
    }

    public void call(String message){
        System.out.println("CallField:" + fieldA +", message :" + message);
    }

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

    // 어노테이션 사용

    @PostConstruct
    public void init(){
        connect();
    }

    @PreDestroy
    public void close(){
        disconnect();
    }

}

 최신 스프링에서 가장 권장하는 방법으로 편리하다. 또한 스프링에 종속적인 기술이 아닌 자바 표준이기에 다른 컨테이너에서도 작동한다는 장점을 가지고 있다.

 다만 외부 라이브러리 적용은 불가능하기 때문에 외부 라이브러리 사용시에는 @Bean을 사용해야 한다.