Spring @Bean

최준호·2021년 7월 6일
0

Spring

목록 보기
7/47

Bean의 생명주기

Service로직을 구현하는 구현체 Bean도 있겠지만 web application에서 DB connection pool이나 socket과 같이 application 시작 시점에 미리 생성해두고 종료 시점에 종료시키는 작업을 진행해야하는 경우도 있습니다. spring에서는 이 작업을 어떻게 실행하고 있는지 알아보겠습니다.

DB에 연결하는 예시를 코드로 살펴보겠습니다.

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

DB를 연결하는 NetworkClient class를 만들고 setUrl을 통해 url을 입력해주고 DB연결하는 상황을 가정해보겠습니다.

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://www.test.com");
            return networkClient;
        }
    }
}

그리고 Test code를 작성해서 실행해보면

다음과 같이 출력이된다. java를 아는 분들이라면 당연한 결과라고 생각하실 수 있습니다. NetworkClient의 bean이 등록되는 과정을 보면

  1. NetworkClient() 생성자 호출
  2. 생성자에 선언해놓은 내용 호출
  3. connect()와 call() 호출
  4. url이 세팅되지 않았기 때문에 null로 print
  5. 생성자 호출이 종료된 후 setUrl()로 url 정보 세팅
  6. networkClient 객체를 반환

이렇기 때문에 null이 print되는것이 당연합니다. 그렇다면 객체가 생성되는 시점에 connect를 하고 싶어도 url정보가 null이므로 DB와 connect를 맺을수가 없습니다.

스프링 빈은 우리가 공부해왔던 내용 그대로

  1. 객체 생성
  2. 의존 관계 주입

의 순서로 진행이됩니다. 그렇다면 우리는 connect()와 call()을

  1. 객체 생성
  2. 의존 관계 주입
  3. connect(), call() 등의 사용자가 원하는 작업

순서로 진행해야합니다. 그렇다면 의존 관계 주입(='setUrl()')이 끝났음을 개발자에게 알려주고 개발자는 그 시점에 connect(), call()과 같은 초기화 작업을 진행하면 됩니다. spring에서는 bean에 의존 관계 주입이 완료되었음을 callback method를 통해서 알려주는데요. 이 콜백을 포함하는 bean의 생명주기(life cycle)은

  1. 스프링 컨테이너 생성
  2. 스프링 빈 생성
  3. 의존 관계 주입
  4. 초기화 콜백
  5. 초기화 작업 후 서비스 로직에서 사용
  6. 소멸전 콜백
  7. 스프링 종료

이러한 작업을 위해서 spring에서 3가지의 방법이 있는데요

  1. Interface(InitializingBean, DisposableBean)
  2. bean 옵션의 initMethod, destroyMethod
  3. @PostConstruct, @PreDestory

순서대로 알아보겠습니다.

Interface(InitializingBean, DisposableBean)

Spring에서 지원하는 Interface인데요.

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 {
        System.out.println("NetworkClient.afterPropertiesSet");
        connect(); 
        call("초기화 연결 메시지");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("NetworkClient.destroy");
        disconnect();
    }
}

아까 만들었던 NetworkClient class에 InitializingBean, DisposableBean 인터페이스를 상속받고 afterPropertiesSet()과 destroy()를 오버라이드하여 사용합니다. 아까 생성자에 있던 connect()와 call()을 afterPropertiesSet()에 넣고 disconnect()는 destroy()에 넣습니다. 이렇게 test 코드를 실행시키시면

아까와는 다르게 생성자가 호출되었을때는 null이지만 의존 관계가 모두 주입되고(setUrl이 실행되어지고) connect()와 call() 그리고 disconnect()까지 실행이 되었습니다.

이 방법은 spring에서 지원하는 방법이지만 단점이 있는데요

  • 인터페이스가 스프링 전용 인터페이스다.
  • 초기화, 소멸 method의 메서드명을 변경할 수 없다.
  • 코드 수정이 불가능한 외부 라이브러리의 경우 적용할 수 없다.

위와 같은 단점으로 사용하기가 어렵습니다... 다음 방법을 알아보겠습니다.

bean 옵션의 initMethod, destroyMethod

빈을 등록할때 옵션을 주어서 사용하는 방법인데요

@Bean(initMethod = "초기화 메서드명", destroyMethod = "소멸 메서드명")

이렇게 다음과 같이 옵션을 주어서 사용할 수 있고 "메서드명"은 동일하게 작성해주셔야합니다.

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로 작성한 후

@Configuration
static class LifeCycleConfig {
    @Bean(initMethod = "init", destroyMethod = "close")
    public NetworkClient networkClient() {
        NetworkClient networkClient = new NetworkClient();
        networkClient.setUrl("http://www.test.com");
        return networkClient;
    }
}

test 코드의 bean의 옵션을 주어 실행해보겠습니다.

위 방식과 동일하게 적용된것을 확인할 수 있습니다.

bean에 옵션을 주어서 사용하는 방식은

  • 메서드 이름을 개발자가 작성할 수 있다.
  • spring에 의존하지 않는다
  • 외부 라이브러리에도 초기화, 소멸 메서드를 적용할 수 있다
  • 소멸 메서드의 기본값은 (inferred)로 등록되어 있는데 이는 close, shutdown을 자동으로 호출한다.
  • 위의 특성으로 인해 라이브러리는 대부분 소멸 메서드를 close와 shutdown으로 사용하기때문에 라이브러리를 불러와서 따로 지정해주지 않아도 자동으로 소멸 메서드를 적용시켜서 사용할 수 있다.
  • 이 기능을 사용하기 싫다면 destroyMethod=""과 같이 옵션을 넣어주면 된다.

@PostConstruct, @PreDestory

위와 같이 bean에 옵션을 주는 방법도 좋지만 java에서 제공하는 annotation을 사용하는 방법도 있습니다.

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

코드를 수정한 후 아까 Bean에 주었던 옵션은 모두 제거하셔도 됩니다. 이렇게 코드도 간단해지고

결과도 똑같이 실행되어집니다.

@PostConstruct, @PreDestory를 사용하는 방법은

  • 스프링에서 가장 권장하는 방법
  • annotation만 붙이면 되므로 편리함
  • 패키지를 확인하면 javax인데 spring에 의존한 annotation이 아닌 java에서 제공하는 annotation이므로 다른 컨테이너에서도 동작함
  • 유일한 단점이 외부 라이브러리에 적용하지 못함
  • 단점을 극복하기 위해 외부 라이브러리의 경우 Bean의 옵션을 활용

출처 https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

profile
코딩을 깔끔하게 하고 싶어하는 초보 개발자 (편하게 글을 쓰기위해 반말체를 사용하고 있습니다! 양해 부탁드려요!) 현재 KakaoVX 근무중입니다!

0개의 댓글