Bean Lifecycle and Scope


본 카테고리는 스프링 프레임워크를 다룬다.

좀 더 자세한 내용은 아래의 공식 사이트를 참고하자.

참고 스프링 프레임워크 공식 사이트


빈의 생명주기와 그 범위

빈의 생명주기(LifeCycle)과 그 범위에 대해서 알아보자.

컨테이너의 초기와와 종료

스프링에서 컨테이너는 초기화와 종료라는 라이프 사이클을 가진다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
// 컨테이너 초기화
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppContext.class);

// 컨테이너에서 빈 객체를 구해서 사용
Greeter greeter = applicationContext.getBean("greater", Greeter.class);
String message = greater.greet("스프링");
System.out.println(message);

// 컨테이너 종료
applicationContext.close();

위의 코드를 보면 AnnotationConfigApplicationContext의 생성자를 이용해 컨텍스트 객체를 생성하는 걸 볼 수 있다.

이 시점에 스프링 컨테이너가 초기화된다.

스프링 컨테이너는 설정 클래스에서 정보를 읽어와 해당하는 빈 객체를 생성하고 각 빈 객체를 DI하는 작업을 수행한다.

컨테이너 초기화 후엔 컨테이너를 사용할 수 있다.

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

컨테이너의 사용이 끝나면 close() 메소드를 사용해 컨테이너를 종료시킨다.

여기서 close() 메소드는 AbstractApplicationContext 클래스에 정의되어 있으며, AnnotationConfigApplicationContextGenericXmlApplicationContext 클래스 모두 AbstractApplicationContext를 상속 받고 있기 때문에 동일하게 close() 메소드를 사용할 수 있다.

컨테이너를 초기화하고 종료할 때에는 다음의 작업도 함께 수행된다.

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

스프링 빈 객체의 생명주기

스프링 컨테이너는 빈 객체의 생명주기를 관리하며, 아래의 그림과 같이 동작한다.

스프링 컨테이너를 초기화하는 경우 객체생성, 의존 설정(DI), 초기화 의 작업이 순차적으로 진행되며,

종료시 마지막 소멸 작업이 진행된다.

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

스프링 컨테이너는 빈 객체를 초기화하고 소멸하기 위해 빈 객체에 미리 지정된 메소드를 호출한다.

스프링에서 제공하는 아래의 두 인터페이스를 이용해 빈 객체를 초기화하고 소멸시킬 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.beans.factory.InitializingBean;

// 해당 인터페이스를 구현할 경우, 빈 객체의 초기화에서 빈 객체의 afterPropertiesSet() 메소드가 호출된다.
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}

import org.springframework.beans.factory.DisposableBean;

// 해당 인터페이스를 구현할 경우, 빈 객체의 소멸에서 빈 객체의 destory() 메소드가 호출된다.
public interface DisposableBean {
void destory() throws Exception;
}

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

모든 빈 객체가 InitializingBeanDisposableBean 인터페이스를 구현해야 하는 것은 아니다.

직접 구현한 빈 객체가 아닌 외부의(라이브러리 등) 클래스를 빈 객체로 설정할 경우 해당 인터페이스를 구현하고 있지 않을 수 있다.

혹은 InitializingBeanDisposableBean 인터페이스를 사용하고 싶지 않은 경우 커스텀 인터페이스를 작성할 수 있다.

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Client2 {
private String host;

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

public void connect() throws Exception {
System.out.println("Client2.connect() 실행");
}

public void send() {
System.out.println("Client2.send() to " + host);
}

public void close() throws Exception {
System.out.println("Client2.close() 실행");
}
}

위의 Client2 클래스를 빈 객체로 사용하려면 초기화 과정에서 connect() 메소드를 실행하고 소멸 과정에서 close() 메소드를 실행하게끔 지정해주어야 한다.

이때 @Bean 어노테이션의 속성값을 사용하여 지정할 수 있다.

초기화는 initMethod 속성을, 소멸은 destroyMethod 속성을 사용하며 적용 결과는 아래와 같다.

1
2
3
4
5
6
@Bean(initMethod="connect", destoryMethod="close")
public Client2 client2() {
Client2 client = new Client2();
client.setHost("host");
return client;
}

빈 객체의 생성과 관리 범위

Singleton scope

스프링 컨테이너는 빈 객체를 하나만 생성한다.

아래의 코드를 보자.

1
2
Client client1 = applicationContext.getBean("client", Client.class);
Client client2 = applicationContext.getBean("client", Client.class);

위의 client1 객체와 client2 객체는 동일한 빈 객체를 참조한다.

위와 같이 하나의 식별자에 대해 한 개의 객체만 존재하는 빈은 싱글턴(Singleton)의 범위를 가진다.

상술했듯 기본적으로 빈 객체는 하나만 생성되기 때문에 별도 설정을 하지 않으면 싱글턴의 범위를 가지게 된다.

보다 확실하게 명시하고 싶다면 @Scope 어노테이션을 사용한다.

1
2
3
4
5
6
7
@Bean
@Scope("singleton")
public Client2 client2() {
Client2 client = new Client2();
client.setHost("host");
return client;
}

Prototype scope

빈 객체의 범위를 프로토타입으로 설정하면 빈 객체를 구할 때마다 매번 새로운 객체를 생성하게 된다.

프로토타입으로 지정하려면 싱글턴과 마찬가지로 @Scope 어노테이션을 사용한다.

아래의 코드를 보자.

1
2
3
4
5
6
7
@Bean
@Scope("prototype")
public Client2 client2() {
Client2 client = new Client2();
client.setHost("host");
return client;
}

위와 같이 범위를 프로토타입으로 지정한 후

1
2
Client client1 = applicationContext.getBean("client", Client.class);
Client client2 = applicationContext.getBean("client", Client.class);

위의 코드에서 client1client2를 비교하면 다른 객체로 판정한다.

프로토타입 빈 객체를 사용할 때는 스프링 컨테이너가 소멸 과정을 자동으로 해주지 않기때문에 명시적으로 직접 소멸 처리해야한다.