[스프링] 스프링5 프로그래밍 입문 - 6장 빈 라이프사이클과 범위

June·2021년 5월 25일
0

컨테이너 초기화와 종료

스프링 컨테이너는 설정 클래스에서 정보를 읽어와 알맞은 빈 객체를 생성하고 각 빈을 연결(의존 주입)하는 작업을 수행한다.

컨테이너를 사용한다는 것은 getBean()과 같은 메서드를 이용해서 컨테이너에 보관된 빈 객체를 구한다는 것을 뜻한다.

  • 컨테이너 초기화 -> 빈 객체 생성, 의존 주입, 초기화
  • 컨테이너 종료 -> 빈 객체의 소멸

스프링 빈 객체의 라이플 사이클

객체 생성 -> 의존 설정 -> 초기화 -> 소멸

스프링 컨테이너를 초기화할 때 스프링 컨테이너는 가장 먼저 빈 객체를 생성하고 의존을 설정한다. 의존 자동 주입을 통한 의존 설정이 이 시점에 수행된다. 모든 의존 설정이 완료되면 빈 객체의 초기화를 수행한다. 빈 객체를 초기화하기 위해 스프링은 빈 객체의 지정된 메서드를 호출한다. 스프링 컨테이너를 종료하면 스프링 컨테이너는 빈 객체의 소멸을 처리한다.

빈 객체의 초기화와 소멸 : 스프링 인터페이스

스프링 컨테이너는 빈 객체를 초기화하고 소멸하기 위해 빈 객체의 지정한 메서드를 호출한다. 두 인터페이스는 다음과 같다.

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

public interface DisposableBean {
    void destroy() throws Exception;
}

빈 객체가 InitializingBean 인터페이스를 구현하면 스프링 컨테이너는 초기화 과정에서 빈 객체의 afterPropertiesSet() 메서드를 실행한다. 빈 객체를 생성한 뒤에 초기화 과정이 필요하면 IntializingBean 인터페이스를 상속하고 afterPropertiesSet() 메서드를 알맞게 구현하면 된다.

스프링 컨테이너는 빈 객체가 DisposableBean 인터페이스를 구현한 경우 소멸 과정에서 빈 객체의 destroy() 메서드를 실행한다. 빈 객체의 소멸 과정이 필요하면 DisposableBean 인터페이스를 상속하고 destroy() 메서드를 알맞게 구현하면 된다.

초기화와 소멸 과정이 필요한 예가 데이터베이스 커넥션 풀이다. 커넥션 풀을 위한 빈 객체는 초기화 과정에서 데이터베이스 연결을 생성한다. 컨테이너를 사용하는 동안 연결을 유지하고 빈 객체를 소멸할 때 사용중인 데이터베이스 연결을 끊어야 한다.

Client

package spring;

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

public class Client implements InitializingBean, DisposableBean {
    
    private String host;

    public void setHost(String host) {
        this.host = host;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Client.afterPropertiesSet() 실행");
    }
    
    public void send() {
        System.out.println("Client.send() to " + host);
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("Client.destroy() 실행");
    }
}

AppCtx

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.Client;

@Configuration
public class AppCtx {

    @Bean
    public Client client() {
        Client client = new Client();
        client.setHost("host");
        return client;
    }
}

Main

package main;

import config.AppCtx;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import spring.Client;

public class Main {

    public static void main(String[] args) {
        AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);

        Client client = ctx.getBean(Client.class);
        client.send();

        ctx.close();
    }
}

5월 25, 2021 4:03:57 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5a2e4553: startup date [Tue May 25 16:03:57 KST 2021]; root of context hierarchy
Client.afterPropertiesSet() 실행
Client.send() to host
Client.destroy() 실행

5월 25, 2021 4:03:58 오후 org.springframework.context.support.AbstractApplicationContext doClose
정보: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5a2e4553: startup date [Tue May 25 16:03:57 KST 2021]; root of context hierarchy

afterPropertiesSet() 메서드를 먼저 실행했다. 즉 스프링 컨테이너는 빈 객체 생성을 마무리 한 뒤에 초기화 메서드를 실행한다. 가장 마지막에 destroy() 메서드를 실행했다. 이 메서드는 스프링 컨테이너를 종료하면 호출된다는 것을 알 수 잇다. ctx.close() 코드가 없다면 컨테이너의 종료과정을 수행하지 않기 때문에 빈 객체의 소멸 과정도 실행되지 않는다.

빈 객체의 초기화와 소멸 : 커스텀 메서드

모든 클래스가 InitializingBean 인터페이스와 DisposableBean 인터페이스를 상속받아 구현할 수 있는 것은 아니다. 직접 구현한 클래스가 아닌 외부에서 제공받은 클래스를 스프링 빈 객체로 설정하고 싶을 때도 있다. 이 경우 소스 코드를 받지 않았다면 두 인터페이스를 구현하도록 수정할 수 없다. 이렇게 InitalizingBean 인터페이스와 DisposableBean 인터페이스를 구현할 수 없거나 이 두 인터페이스를 사용하고 싶지 않을 때는 스프링 설정에서 직접 메서드를 지정할 수도 있다.

Client2

package spring;

public class Client2 {
    
    private String host;

    public void setHost(String host) {
        this.host = host;
    }
    
    public void connect() {
        System.out.println("Client2.connect() 실행");
    }
    
    public void send() {
        System.out.println("Client2.send() to " + host);
    }
    
    public void close() {
        System.out.println("Client2.close() 실행");
    }
}

Client2 클래스를 빈으로 사용하려면 초기화 과정에서 connect() 메서드를 실행하고 소멸 과정에서 close() 메서드를 실행해야 한다면 다음과 같이 @Bean 어노테이션의 initMethod 속성과 destroyMethod 속성에 초기화와 소멸 과정에서 사용할 메서드 이름인 connect와 close를 지정해주기만 하면 된다.

AppCtx

    @Bean(initMethod = "connect", destroyMethod = "close")
    public Client2 client2() {
        Client2 client = new Client2();
        client.setHost("host");
        return client;
    }

5월 25, 2021 4:14:44 오후 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5a2e4553: startup date [Tue May 25 16:14:44 KST 2021]; root of context hierarchy
Client.afterPropertiesSet() 실행
Client2.connect() 실행
Client.send() to host
Client2.close() 실행
Client.destroy() 실행
5월 25, 2021 4:14:45 오후 org.springframework.context.support.AbstractApplicationContext doClose
정보: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5a2e4553: startup date [Tue May 25 16:14:44 KST 2021]; root of context hierarchy

initMethod 속성과 destroyMethod 속성에 지정한 메서드는 파라미터가 없어야 한다. 이 두 속성에 지정한 메서드에 파라미터가 존재할 경우 스프링 컨테이너는 익셉션을 발생시킨다.

빈 객체의 생성과 관리 범위

한 식별자에 대해 한 개의 객체만 존재하는 빈은 싱글톤 범위를 갖는다. 별도 설정을 하지 않으면 빈은 싱글톤 범위를 갖는다.

사용 빈도가 낮긴 하지만 프로토타입범위의 빈을 설정할 수도 있다. 빈의 범위를 프로토타입으로 지정하면 빈 객체를 구할 때마다 매번 새로운 객체를 생성한다.

AppCtx

@Configuration
public class AppCtx {

    @Bean
    @Scope("prototype")
    public Client client() {
        Client client = new Client();
        client.setHost("host");
        return client;
    }
}

특정 빈을 프로토타입 범위로 지정하려면 다음과 같이 값으로 "prototype"을 갖는 @Scope 애노테이션을 @Bean 애노테이션과 함께 사용하 된다.

프로토타입 범위를 갖는 빈은 완전한 라이프사이클을 따르지 않는다. 스프링 컨테이너는 프로토타입의 빈 객체를 생성하고 프로퍼티를 설정하고 초기화 작업까지는 수행하지만, 컨테이너를 종료한다고 해서 생성한 프로토타입 빈 객체의 소멸 메서드를 실행하지는 않는다.

0개의 댓글