빈 라이프사이클과 범위
1. 컨테이너 초기화와 종료
스프링 컨테이너는 초기화와 종료라는 라이프사이클을 갖는다.
1
2
3
4
5
6
7
8
9
10
// 1. 컨테이너 초기화
AnnoatationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppContext.class);
// 2. 컨테이너에서 빈 객체를 구해서 사용
Greeter g = ctx.getBean("greeter", Greeter.class);
String msg = g.greet("스프링");
// 3. 컨테이너 종료
ctx.close;
AnnotationConfigApplicationContext의 생성자를 이용해서 컨텍스트 객체를 생성하는데 이 시점에 스프링 컨테이너를 초기화한다. 스프링 컨테이너는 설정 클래스에서 정보를 읽어와 알맞은 빈 객체를 생성하고 각 빈을 연결(의존 주입)하는 작업을 수행한다.
컨테이너 초기화가 완료되면 컨테이너를 사용할 수 있다, 커넽이너를 사용한다는 것은 getBean()과 같은 메서드를 이용해서 컨테이너에 보관된 빈 객체를 구한다는 것을 뜻한다.
컨테이너 사용이 끝나면 컨테이너를 종료한다. 이때 사용하는 메서드가 close() 메서드이다. 이 메서드는 AbstractApplicationContext 클래스에 정의되어 있다.
컨테이너 초기화하고 종료할 때에는 다음의 작업도 함께 수행한다
컨테이너 초기화 -> 빈 객체의 생성, 의존 주입, 초기화
컨테이너 종료 -> 빈 객체의 소멸
스프링 컨테이너의 라이프사이클에 따라 빈 객체도 자연스럽게 생성과 소멸이라는 라이프사이클을 갖는다.
2. 스프링 빈 객체의 라이프사이클
스프링 컨테이너는 빈 객체의 라이프사이클을 관리한다. 컨테이너가 관리하는 빈 객체의 라이프사이클은 다음과 같다.
객체 생성 -> 의존 설정 -> 초기화 -> 소멸
- 스프링 컨테이너를 초기화할 때 스프링 컨테이너는 가장 먼저 빈 객체를 생성한다
- 그 후 의존을 설정한다. 의존 자동 주입을 통한 의존 설정이 이 시점에 수행된다.
- 모든 의존 설정이 완료되면 빈 객체의 초기화를 수행한다. 빈 객체를 초기화하기 위해 스프링은 빈 객체의 지정된 메서드를 호출한다.
- 스프링 컨테이너를 종료하면 스프링 컨테이너는 빈 객체의 소멸을 처리한다. 이때에도 지정한 메서드를 호출한다.
2.1 빈 객체의 초기화와 소멸 : 스프링 인터페이스
스프링 컨테이너는 빈 객체를 초기화하고 소멸하기 위해 빈 객체의 지정한 메서드를 호출한다. 스프링은 다음의 두 인터페이스에 이 메서드를 정의하고 있다.
- org.springframework.beans.factory.InitializingBean
- org.springframework.beans.factory.DisposableBean
두 인터페이스는 다음과 같다.
1
2
3
4
5
6
7
public interface InitializingBean{
void afterPropertiesSet() throws Exception;
}
public interface DisposableBean{
void destroy() throws Exception;
}
빈 객체가 InitializingBean 인터페이스를 구현하면 스프링 컨테이너는 초기화 과정에서 빈 객체의 afterPropertiesSet() 메서드를 실행한다. 빈 객체를 생성한 뒤에 초기화 과정이 필요하면 인터페이스를 상속하고 메서드를 알맞게 구현하면 된다.
스프링 컨테이너는 빈 객체가 DiposableBean 인터페이스를 구현한 경우 소멸 과정에서 빈 객체의 destroy()메서드를 실행한다. 이 또한 필요하면 인터페이스를 상속하고 메서드를 구현하면 된다.
초기화와 소멸 과정이 필요한 예가 데이터베이스 커넥션 풀이다. 커넥션 풀을 위한 빈 객체는 초기화 과정에서 데이터베이스 연결을 생성한다. 컨테이너를 사용하는 동안 연결을 유지하고 빈 객체를 소멸할 때 사용 중인 데이터베이스 연결을 끊어야 한다.
또 다른 예로 채팅 클라이언트가 있다. 채팅 클라이언트는 시작할 때 서버와 연결을 생성하고 종료할 때 연결을 끊는다.
InitializingBean, DisposableBean 인터페이스를 구현한 간단한 클래스를 통해서 실제로 초기화 메서드와 소멸 메서드가 언제 실행되는지 확인해보자. 사용할 클래스는 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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() 실행");
}
}
설정 클래는 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 클래스는 아래와 같이 작성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import config.AppCtx;
import spring.Client;
public class Main {
public static void main(String[] args) {
AbstractApplicationContext ctx =
new AnnotationConfigApplicationContext(AppCtx.class);
Client client = ctx.getBean("client", Client.class);
client.send();
ctx.close();
}
}
실행해보면 다음과 같은 메시지가 출력될 것이다. 콘솔에 출력된 메시지의 순서를 보면 먼저 afterPropertiesSet()메서드를 실행했다. 즉, 스프링 컨테이너는 빈 객체 생성을 마무리한 뒤에 초기화 메서드를 실행한다. 가장 마지막에 destroy()메서드를 실행했다. 이 메서드는 스프링 컨테이너를 종료하면 호출된다는 것을 알 수 있다
2.2 빈 객체의 초기화와 소멸 : 커스텀 메서드
모든 클래스가 InitializingBean, DisposableBean 인터페이스를 상속받아 구현할 수 있는 것은 아니다.
직접 구현한 클래스가 아닌 외부에서 제공받은 클래스를 스프링 빈 객체로 설정하고 싶을 때도 있다. 이 경우 소스 코드를 받지 않았다면 두 인터페이스를 구현하도록 수정할 수 없다.
이렇게 InitializingBean, DisposableBean 인터페이스를 구현할 수 없거나 이 두 인터페이스를 사용하고 싶지 않은 경우에는 스프링 설정에서 직접 메서드를 지정할 수 있다.
방법은 @Bean 태그에서 initMethod 속성과 destroyMethod 속성을 사용해서 초기화 메서드와 소멸 메서드의 이름을 지정하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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("Client.send() to " + host);
}
public void close() {
System.out.println("Client2.close() 실행");
}
}
Client2 클래스를 빈으로 사용하려면 초기화 과정에서 connect() 메서드를 실행하고 소멸 과정에서 close() 메서드를 실행해야 한다면 다음과 같이 @Bean 애노테이션의 initMethod 속성과 destroyMethod 속성에 초기화와 소멸 과정에서 사용할 메서드 이름인 connect와 close를 지정해주기만 하면 된다.
1
2
3
4
5
6
@Bean(initMethod = "connect", destroyMethod = "close")
public Client2 client2() {
Client2 client2 = new Client2();
client2.setHost("host");
return client2;
}
위 설정을 AppCtx 클래스에 추가한 뒤 다시 Main을 실행하면 다음과 같은 결과가 나온다.
설정 클래스 자체는 자바 코드이므로 initMethod 속성을 사용하는 대신 다음과 같이 빈 설정 메서드에서 직접 초기화를 수행해도 된다.
1
2
3
4
5
6
7
@Bean(destroyMethod = "close")
public Client2 client2() {
Client2 client2 = new Client2();
client2.setHost("host");
client2.connect();
return client2;
}
설정 코드에서 초기화 메서드를 직접 실행할 때 주의할 점은 초기화 메서드가 두 번 불리지 않도록 하는 것이다.
initMethod속성과 destroyMethod 속성에 지정한 메서드는 파라미터가 없어야 한다. 이 두 속성에 지정한 메서드에 파라미터가 존자할 경우 스프링 컨테이너는 Exception을 발생시킨다.
3. 빈 객체의 생성과 관리 범위
3.1 싱글톤 범위
우리는 스프링 컨테이너는 빈 객체를 한 개만 생성한다고 했다. 예를들어 아래 코드와 같이 동일한 이름을 갖는 빈 객체를 구하면 client1과 client2는 동일한 빈 객체를 참조한다고 설명했다.
1
2
3
Client client1 = ctx.getBean("client", Client.class);
Client client2 = ctx.getBean("client", Client.class);
// client1 == client2 -> true
이렇게 한 식별자에 대해 한 개의 객체만 존재하는 빈은 싱글톤(singleton) 범위(scope)를 갖는다 별도 설정을 하지 않으면 빈은 싱글톤 범위를 갖는다.
3.2 프로토타입 범위
사용빈도가 낮긴 하지만 프로토타입 범위의 빈을 설정할 수도 있다. 빈의 범위를 프로토타입으로 지정하면 빈 객체를 구할 때마다 매번 새로운 객체를 생성한다.
1
2
3
Client client1 = ctx.getBean("client", Client.class);
Client client2 = ctx.getBean("client", Client.class);
// client1 == client2 -> false
특정 빈을 프로토타입 범위로 지정하렴녀 다음과 같이 값으로 “prototype”을 갖는 @Scope 애노태이션을 @Bean 애노테이션과 함께 사용하면 된다.
1
2
3
4
5
6
7
8
9
10
import org.springframework.context.annotation.Scope;
@Configuration
public class AppCtxWithPrototype{
@Bean
@Scope("prototype")
public Client client(){
...
}
}
싱글톤 범위를 명시적으로 지정하고 싶다면 @Scope 애노테이션 값으로 “singleton”을 주면 된다.
프로토타입 범위를 갖는 빈은 완전한 라이프사이클을 따르지 않는다는 점에 주의해야 한다. 스프링 컨테이너는 프로토타입의 빈 객체를ㄹ 생성하고 프로퍼티를 설정하고 초기화 작업까지는 수행하지만, 컨테이너를 종료한다고 해서 생성한 프로토타입 빈 객체의 소멸 메서드를 실행하지는 않는다. 따라서 프로토타입 범위의 빈을 사용할 때에는 빈 객체의 소멸 처리를 코드에서 직접 해야 한다.
Ref.
- 최범균, 스프링프로그래밍입문5, 가메출판사.