본문 바로가기

Java/Spring

Spring 5 입문: Chapter 06. Bean 라이프사이클과 범위

더보기

이 프로젝트의 개발 환경

  • 개발 언어 및 개발 환경
    • OpenJDK 12
    • Spring: spring-context 5.0.2.RELEASE
    • Gradle 7.3
  • 기타 환경
    • macOS Sonoma 14.1.1
    • IntelliJ IDEA 2020.3 Ultimate Edition

스프링 컨테이너는 초기화와 종료라는 라이프 사이클을 갖습니다. 이전 예제에서 작성한 Main 클래스르 살펴봅니다.

AnnotationConfigApplicationContext ctx
	= new AnnotationConfigApplicationContext(AppContext.class);
Greeter greeter = ctx.getBean("greeter", Greeter.class);
String msg = greeter.greet("스프링");
System.out.println(msg);
ctx.close();
코드 비고
Line 1:2 new AnnotationConffigApplicationContext() 스프링 컨테이너를 초기화합니다.
Line 3:5 getBean() 스프링 컨테이너를 사용해 Bean 객체를 구합니다.
Line 6 ctx.close() 스프링 컨테이너를 종료합니다.

스프링 컨테이너는 초기화 과정에서 설정 클래스를 읽고 Bean 객체를 생성 및 의존 주입을 실행합니다.

컨테이너가 일단 초기화되면 getBean()과 같이 Bean 객체에 엑세스 할 수 있습니다.

컨테이너가 모두 사용되면 종료합니다. 컨테이너를 종료하면 컨테이너에 등록된 모든 Bean 객체가 소멸합니다.

예제 프로젝트 생성

sp5-chap06 프로젝트를 생성하고 chap06_a 패키지를 추가합니다.

이전 프로젝트의 chap02 패키지의 소스 파일을 복사하여 사용합니다.

$ tree
.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
    └── main
        ├── java
        │   └── chap06
        │       ├── AppContext.java
        │       ├── Greeter.java
        │       └── Main.java
        └── resources

7 directories, 8 files

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

스프링 컨테이너는 Bean 객체를 초기화하고 소멸하는 과정에서 다음 메소드를 호출합니다.

구분 FQCN(Full Qualified Class Name) 호출 메소드
Bean 초기화 이후 호출 org.springframework.beans.factory.InitializingBean void afterPropertiesSet()
Bean 소멸 직전 호출 org.springframework.beans.factory.DisposableBean void destroy()
더보기

예를 들면 Bean 객체가 생성-초기화되는 시점에서 DB와의 Connection을 연결하고, Bean 객체가 소멸되는 시점에서 연결되어 있는 Connection을 Close 할 수 있습니다.

테스트를 위해 chap06_a 패키지에 Client 클래스를 생성합니다.

package chap06_a;

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() 실행");
	}
}
코드 비고
Line 6 implements InitializingBean InitializingBean을 구현합니다.
implements DisposableBean DisposableBean을 구현합니다.
Line 15:19 void afterPropertiesSet() InitializingBean의 Bean 초기화 시 호출되는 메소드를 오버라이딩합니다.
Line 26:30 void destroy() DisposableBean의 Bean 소멸 시 호출되는 메소드를 오버라이딩합니다.

Client 클래스를 Bean에 등록하기 위해 설정 클래스에 추가합니다.

package chap06_a;

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

@Configuration
public class AppContext
{
	@Bean
	public Client client()
	{
		Client client = new Client();
		client.setHost("127.0.0.1");
		return client;
	}
}

스프링 컨테이너가 Client를 Bean으로 사용합니다. Main 클래스는 다음과 같이 수정합니다.

package chap06_a;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main
{
	public static void main(String[] args)
	{
		AnnotationConfigApplicationContext ctx
			= new AnnotationConfigApplicationContext(AppContext.class);
		Client client = ctx.getBean(Client.class);
		client.send();
		ctx.close();
	}
}

애플리케이션을 실행하고 콘솔 출력 결과를 확인합니다.

Bean 객체가 초기화되면서 afterPropertiesSet()가 실행됩니다. 스플링 컨테이너가 close() 되면 Bean 객체의 destroy()가 실행됩니다.

Client.afterPropertiesSet() 실행
Client.send() to 127.0.0.1
Client.destroy() 실행

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

어떤 Bean 클래스는 InitializingBeanDisposableBean 인터페이스를 구현 할 수 없습니다(외부 라이브러리와 같은).

초기화와 소멸 인터페이스를 구현 할 수 없는 경우 @Bean에서 커스텀 메소드를 지정하여 동일한 기능을 사용 할 수 있습니다.

chap06_a 패키지에서 ClientWrapper 클래스를 작성합니다.

package chap06_a;

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

설정 클래스에서 Bean을 등록하면서 초기화와 소멸 시 호출되는 메소드를 지정합니다.

package chap06_a;

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

@Configuration
public class AppContext
{
	@Bean
	public Client client()
	{
		Client client = new Client();
		client.setHost("127.0.0.1");
		return client;
	}
	
	@Bean(initMethod = "connect", destroyMethod = "close")
	public ClientWrapper clientWrapper()
	{
		ClientWrapper client = new ClientWrapper();
		client.setHost("127.0.0.1");
		return client
	}
}
코드 비고
Line 17 initMethod = "connect" Bean이 초기화되면서 호출되는 메소드를 지정합니다.
destoryMethod = "close" Bean이 소멸되면서 호출되는 메소드를 지정합니다.

Main 클래스에서는 스프링 컨테이너를 사용해 ClientWrapper Bean을 사용합ㄴ니다.

package chap06_a;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main
{
	public static void main(String[] args)
	{
		AnnotationConfigApplicationContext ctx
			= new AnnotationConfigApplicationContext(AppContext.class);
		ClientWrapper client = ctx.getBean(ClientWrapper.class);
		client.send();
		ctx.close();
	}
}

애플리케이션을 실행하고 콘솔 출력 결과를 확인합니다.

Bean 객체가 초기화되면서 connect()가 실행됩니다. 스플링 컨테이너가 close() 되면 Bean 객체의 close()가 실행됩니다.

ClientWrapper.connect() 실행
ClientWrapper.send() to 127.0.0.1
ClientWrapper.close() 실행
더보기

Bean 객체가 설정 코드에서 인스턴싱된다면 초기화 함수를 코드로 직접 작성 할 수 있습니다.

@Bean(destroyMethod = "close")
public ClientWrapper clientWrapper()
{
	ClientWrapper client = new ClientWrapper();
	client.setHost("127.0.0.1");
	client.connect();
	return client;
}
코드 비고
Line 6 connect() @BeaninitMethod를 지정하는 대신 초기화 코드를 직접 작성합니다.
더보기

@Bean에서 지정하는 initMethoddestoryMethod는 인자를 가질 수 없습니다. 이 옵션에서 지정된 함수가 인자를 포함하면 스프링은 Exception을 발생시킵니다.

Bean 객체의 생성과 관리 범위

Bean 객체를 getBean()으로 여러 번 엑세스하더라도 실제 리턴되는 객체는 동일합니다. 싱글톤 범위(Singleton scope)에서 Bean이 설정되었기 때문입니다.

반면 프로토타입 범위에서 Bean을 지정하면 getBean()으로 엑세스 할 때마다 새로운 객체를 리턴합니다.

새로운 패키지 chap06_b를 생성하고 chap06_a의 소스 코드를 모두 복사합니다.

@Bean
@Scope("prototype")
public Client client()
{
	Client client = new Client();
	client.setHost("127.0.0.1");
	return client;
}
코드 비고
Line 2 @Scope("prototype") Bean을 등록할 때 프로토타입 범위로 지정합니다.

Bean을 싱글톤 범위로 명시 할 수도 있습니다.

@Bean(destroyMethod = "close")
@Scope("singleton")
public ClientWrapper clientWrapper()
{
	ClientWrapper client = new ClientWrapper();
	client.setHost("127.0.0.1");
	client.connect();
	return client;
}
코드 비고
Line 2 @Scope("singleton") Bean을 등록할 때 싱글톤 범위로 명시합니다.

테스트를 위해서 Main 클래스에서 Client Bean 객체에 엑세스합니다. Client는 프로토타입 범위로 지정되어 있습니다.

package chap06_b;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main
{
	public static void main(String[] args)
	{
		AnnotationConfigApplicationContext ctx
			= new AnnotationConfigApplicationContext(AppContext.class);
		Client client_0 = ctx.getBean(Client.class);
		Client client_1 = ctx.getBean(Client.class);
		ctx.close();
	}
}

애플리케이션을 실행하고 출력 결과를 확인합니다.

ClientWrapper.connect() 실행
Client.afterPropertiesSet() 실행
Client.afterPropertiesSet() 실행
ClientWrapper.close() 실행
코드 비고
Line 1 Line 4 ClientWrapper 싱글톤 범위의 Bean 객체는 Bean 라이프사이클을 따릅니다.
Line 2:3 Client 프로토타입 범위의 Bean 객체는 엑세스 횟수에 따라서 여러 번 초기화됩니다.
또한 스프링 컨테이너가 close() 될 때 소멸되지 않습니다.

정리 및 복습

  • Bean 객체는 스프링 컨테이너에서 초기화되고 스프링 컨테이너가 close() 되면서 소멸합니다.
  • 이를 Bean의 라이프사이클이라고 부릅니다.
  • Bean이 org.springframework.beans.factory.InitializingBean 인터페이스를 구현하면 초기화 이후 afterPropertiesSet() 함수가 호출됩니다.
  • Bean이 org.springframework.beans.factory.DisposableBean 인터페이스를 구현하면 소멸 직전 destroy() 함수가 호출됩니다.
  • 직접 구현한 함수를 지정하려면 @Bean에서 initMethod와 destroyMethod 옵션을 사용합니다.
@Bean(initMethod = "connect", destroyMethod = "close")
public ClientWrapper clientWrapper()
{
	ClientWrapper client = new ClientWrapper();
	client.setHost("127.0.0.1");
	return client
}
  • @Bean에서 초기화 및 소멸 함수로 지정된 함수는 인자를 가질 수 없습니다.
  • Bean 객체는 default로 싱글톤 범위에서 사용됩니다.
  • Bean 객체를 프로토타입 범위로 지정하면 엑세스할 때마다 객체가 새로 생성됩니다.
  • 프로토타입 범위로 지정하려면 설정 클래스에서 Bean을 등록할 때 @Scope("prototype")을 사용합니다.
@Bean
@Scope("prototype")
public Client client()
{
	Client client = new Client();
	client.setHost("127.0.0.1");
	return client;
}
  • 프로토타입 범위의 Bean 객체는 일반적인 Bean 라이프사이클을 따르지 않습니다.
  • 프로토타입 범위의 Bean 객체는 스프링 컨테이너가 close() 되더라도 소멸 함수를 호출하지 않습니다.