본문 바로가기
Java/[스프링 5 프로그래밍 입문]

[Spring] 빈 객체의 라이프사이클과 범위 (Life Cycle & Scope of Bean)

2021. 8. 3.
반응형

스프링 컨테이너는 빈 객체의 생성, 초기화, 의존 주입 등 객체 관리를 위한 다양한 기능 제공하며, 라이프사이클도 관리한다. 컨테이너가 생성될 때 빈 객체가 생성되고 초기화되며 컨테이너가 종료될 때 빈 객체가 소멸하게 된다. 둘의 라이프사이클에 대해 알아보자.

 

스프링 컨테이너와 빈 객체의 라이프사이클

스프링 컨테이너와 빈 객체를 라이프사이클을 갖는다. 빈 객체를 컨테이너에 등록되기 때문에 스프링 컨테이너의 라이프사이클에 따라 생성되거나 소멸된다. 먼저 스프링 컨테이너의 라이프사이클을 알아보자.

 

스프링 컨테이너의 라이프사이클

스프링 컨테이너는 초기화종료라는 라이프사이클을 갖는다. AnnotationConfigApplicationContext의 생성자를 이용해서 컨텍스트 객체가 생성될 때 스프링 컨테이너가 초기화된다. 초기화될 때 스프링 컨테이너는 설정 클래스에서 정보를 읽어와서 알맞은 빈을 생성하고 각 빈을 의존 주입하는 작업을 수행한다. 컨테이너 초기화가 끝난 후에 컨테이너를 사용할 수 있는데, 이때 컨테이너를 "사용"한다는 것은 컨테이너에 보관된 빈 객체를 getBean()과 같은 메서드를 이용해 구한다는 의미이다.

 

컨테이너 사용이 끝나면 close() 메서드를 사용해서 컨테이너를 종료한다. close() 메서드는 AbstractApplicationContext 클래스에 정의되어 있으며 자바 설정을 사용하는 AnnotationConfigApplicationContext이 AbstractApplicationContext 클래스를 상속받고 있기 때문에 close() 메서드를 사용해서 컨테이너를 종료할 수 있다.

 

빈 객체의 라이프 사이클

스프링 컨테이너의 라이프사이클에 따라서 빈 객체도 생성과 소멸이라는 라이프사이클을 갖는다. 컨테이너를 초기화하고 종료할 때 다음의 작업들도 함께 수행된다.

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

즉 스프링 컨테이너가 빈 객체의 라이프사이클을 관리하게 된다. 스프링 컨테이너를 초기화할 때 스프링 컨테이너는 빈 객체를 생성하고 의존을 설정하며 의존 자동 주입이 수행된다. 모든 의존 설정이 완료되면 빈 객체의 초기화를 수행하는데, 이를 위해 스프링은 빈 객체의 지정된 메서드를 호출한다. 스프링 컨테이너를 종료하면서 빈 객체의 소멸을 처리하며, 이때에도 지정한 메서드를 호출한다.

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

 

빈 객체의 초기화와 소멸

스프링 컨테이너는 빈 객체를 초기화하고 소멸하기 위해 지정된 메서드를 호출한다. 특정 인터페이스를 구현해서 정해진 메서드에 필요한 기능을 구현할 수도 있고, 커스텀 메서드를 지정하여 초기화 또는 소멸 시 수행되도록 설정할 수도 있다.

 

스프링 인터페이스 이용

스프링은 아래의 두 인터페이스에서 이 메서드를 정의하고 있다.

org.springframework.beans.factory.InitializingBean
org.springframework.beans.factory.DisposableBean
public Interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}

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

빈 객체가 InitializingBean 인터페이스를 구현하면 초기화 과정에서 스프링 컨테이너가 빈 객체의 afterPropertiesSet() 메서드를 실행한다. 따라서 빈 객체의 초기화 과정이 필요하다면 인터페이스를 상속하고 메서드를 구현하면 된다. 소멸 시에도 마찬가지로 DisposableBean 인터페이스를 구현하면 소멸 과정에서 빈 객체의 destroy() 메서드를 실행한다. 필요에 따라 인터페이스를 상속하고 메서드를 알맞게 구현하면 된다.

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() 실행");
	}
    
	// 스프링 컨테이너를 종료할 때 수행됨(종료 메서드)
	@Override
	public void destroy() throws Exception {
		System.out.println("Client.destroy() 실행");
	}
    
}

예를 들어 데이터베이스 커넥션 풀을 사용한다면 빈 객체는 초기화 과정에서 DB 연결을 생성하여 컨테이너를 사용하는 동안은 연결을 유지하고, 소멸될 때 연결을 끊도록 하면 된다. 또한 채팅 클라이언트에서는 초기화될 때 서버와의 연결을 생성하고 소멸할 때 연결을 끊도록 구현하면 된다.

 

커스텀 메서드 이용

모든 클래스가 InitializingBean 인터페이스와 DisposableBean 인터페이스를 상속받아 구현할 수 있는 것이 아니며, 해당 인터페이스를 사용하고 싶지 않을 수도 있다. 이 경우 스프링 설정에서 직접 메서드를 지정할 수 있다. 아래는 그 예시이다.

public class Client2 {

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

	// 빈 객체가 생성된 후 수행됨(초기화 메서드)
	public void connect() {
		System.out.println("Client2.connect() 실행");
	}
    
	// 스프링 컨테이너를 종료할 때 수행됨(종료 메서드)
	public void close() {
		System.out.println("Client2.close() 실행");
	}
}
@Bean(initMethod = "connect", destroyMethod = "close")
public Client2 client2() {
	Client2 client = new Client2();
	client.setHost("host");
	return client;
}

@Bean 태그에서 initMethod와 destroyMethod 속성에 메서드의 이름을 지정하였다. 이렇게 지정하면 Client2 타입의 빈이 생성된 후 초기화 과정에서 connect라는 메서드가, 소멸 과정에서는 close라는 메서드가 실행된다. 초기화 시 실행될 메서드는 initMethod 속성으로 지정하지 않아도 빈 설정 메서드에서 직접 수행해도 된다.

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

위처럼 초기화 메서드를 직접 실행할 때는 초기화 메서드가 두 번 수행되지 않도록 주의해야 한다. 아래의 코드는 initMethod 속성에 connect 메서드가 이미 지정이 되어있는데 client2() 메서드 내부에서도 실행하므로 connect() 메서드가 두 번 실행될 것이다.

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

 

빈 객체의 생성과 관리 범위

이전에 배웠듯이 스프링 컨테이너는 빈 객체를 한 개만 생성한다. 한 식별자에 대해서 한 개의 객체만 존재하는 빈은 싱글톤 범위(singleton scope)를 갖는다. 자주 사용하지는 않지만 프로토타입 범위(prototype scope)의 빈을 설정할 수도 있다. 그렇게 되면 빈 객체를 구할 때마다 매번 새로운 객체를 생성한다. 빈을 프로토타입 범위로 지정하려면 @Scope 애노테이션의 값을 prototype으로 설정해주면 된다. 또한 싱글톤 범위를 명시적으로 지정하고 싶다면 @Scope 애노테이션의 값을 singleton이라고 설정하면 된다.

public class AppCtx {
	
	@Bean
	@Scope("prototype")
	public Client client() {
		...
	}
	
	@Bean
	@Scope("singleton")
	public Client2 client2() {
		...
	}
}

이때 주의할 점은 프로토타입 스코프를 갖는 빈은 완전한 라이프사이클을 따르지 않는다는 것이다. 스프링 컨테이너는 프로토타입 빈 객체를 생성하고 초기화 작업까지는 수행을 하지만 소멸을 시켜주지는 않기 때문이다. 따라서 프로토타입 스코프의 빈을 사용할 때는 빈 객체의 소멸 처리를 코드에서 직접 해야 한다.

 

 

 

참고 서적: <초보 웹 개발자를 위한 스프링 5 프로그래밍 입문>

 

초보 웹 개발자를 위한 스프링5 프로그래밍 입문

COUPANG

www.coupang.com

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.


Spring Framework 시리즈

  1. 스프링 프로젝트 시작하기 (Maven)
  2. 스프링 컨테이너(Container) 의미
  3. Dependency, DI, Assembler (의존, 의존 주입, 주입기) 개념 정리
  4. 스프링에서의 의존 주입(DI)의 의미와 사용법
  5. 스프링 애노테이션을 사용한 의존 주입(DI)
  6. 의존 자동 주입(1) - @Autowired 애노테이션
  7. 의존 자동 주입(2) - 빈 이름과 한정사
  8. 의존 자동 주입(3) - @Autowired의 필수 여부, 자동 주입과 명시적 의존 주입
  9. 컴포넌트 스캔 - @Component, @ComponentScan 사용하기
  10. d
  11. d
  12. d
728x90
반응형

댓글