본문 바로가기
Learning-log/Spring & JPA

(스프링 핵심 원리 - 기본편) 8-(1) 빈 생명주기 콜백 시작, (2) 인터페이스, (3) 빈 등록 초기화, 소멸 메서드

by why제곱 2023. 4. 11.

- 빈 생명주기 콜백 시작

  • 애플리케이션과 DB를 서버 시작 시 미리 연결을 해두거나 애플리케이션 시작 시 필요한 연결을 미리 해두기. 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요.
  • 외부 네트워크에 미리 연결하는 객체를 하나 생성한다고 가정
  • NetworkClient가 애플리케이션 시작 시점에 connect()를 호출해서 연결을 맺어두어야 하고, 종료되면 disConnect() 를 호출해 연결을 끊어야 함.
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 message){
        System.out.println("call : " + url + "message : "+ message);

    }

    public void disconnect(){
        System.out.println("close : "+ url);
    }
}
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(){
        //Configu~ 는 ApplicationContext 를 상속하고 있고 close는 기본 ApplicationContext가 아닌 이것의 하위
        //클래스까지 내려가야 존재
        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");
            return networkClient;
        }
    }
}
  • 이렇게 하면 내가 원하는 결과와 달리 url 값이 null이 됨.
  • 객체 생성 당시 url이 설정되지 않으며, 객체를 생성한 다음 외부에서 수정자 주입을 통해서 setUrl()이 호출 되어야 url이 존재하게 되기 때문.
  • 스프링 빈은 간단히 객체생성 -> 의존관계 주입의 라이프사이클을 가짐.
  • 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 함. 어떻게 주입이 완료된 시점을 알까? 
  • 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점을 알려주는 다양한 기능 제공
  • 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 줌
  • 스프링 빈의 이벤트 라이프 사이클(싱글톤에 대한 설명)
    • 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료
  • 초기화 콜백 : 빈이 생성되고 의존관계 주입이 된 후에 호출
  • 소멸 전 콜백 : 빈이 소멸되기 직전에 호출

 

  • 객체의 생성과 초기화를 분리해야 함.
    • 생성자는 필수 정보를 받고 메모리를 할당해서 객체를 생성하는 책임을 가짐.
    • 초기화는 이렇게 생성된 값들을 활용해 외부 커넥션을 연결하는 등 무거운 동작 수행
    • 생성자 안에서 무거운 초기화 작업을 함께 하는 것보다는 객체를 생성하는 부분과 초기화하는 부분을 명확하게 나누는 것이 유지보수 관점에서 좋음.
    • 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우 생성자에서 처리해도 괜찮음
  • 스프링은 아래의 3가지 방법으로 빈 생명주기 콜백 지원
    • 인터페이스
    • 설정 ㅈ어보에 초기화 메서드, 종료 메서드 지정
    • @PostConstruct, @PreDestroy 애노테이션 지원

 

- 인터페이스

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

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

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

  • 단점
    • 스프링 전용 인터페이스
    • 초기화, 소멸 메서드의 이름 변경 불가
    • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용 불가

- 빈 등록 초기화, 소멸 메서드

package hello.core.lifecycle;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

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 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();
    }
}
  • 테스트
@Configuration
    static class LifeCycleConfig{
        @Bean(initMethod =  "init", destroyMethod = "close")
        public NetworkClient networkClient(){
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        }
  • 장점
    • 메서드 이름 자유롭게 줄 수 있음
    • 스프링 빈이 스프링 코드에 의존 x
    • 코드가 아니라 설정 정보를 사용하기 때문에 고칠 수 없는 외부 라이브러리에도 종료 메서드 적용 가능
  • 종료 메서드 추론
    • 라이브러리는 대부분 close, shutdown이라는 이름의 종료 메서드 사용
    • 기본 값이 inferred로 등록 되어있고 자동으로 종료 메서드를 추론해서 호출해줌
    • 따라서 따로 지정해주지 않아도 잘 동작하곤 함.
    • 추론 기능 원치 않으면 공백으로 지정하면 됨.