이 포스팅은 스프링 컨테이너의 라이프사이클에 대해 다룹니다.
스프링 컨테이너는 초기화~종료 라는 라이프 사이클을 가집니다.
public class Main {
public static void main(String[] args) throws IOException {
//1. 컨테이너 초기화
AbstractApplicationContext ctx =
new AnnotationConfigApplicationContext(AppCtx.class);
//2. 컨테이너에서 빈 객체를 가져와 사용
Client client = ctx.getBean(Client.class);
client.send();
//3. 컨테이너 종료
ctx.close();
}
}
AnnotationConfigApplicationContext는 자바 어노테이션을 이용한 클래스로부터 객체 설정 정보를 가져옵니다.
스프링 컨테이너는 이때 설정 클래스에서 정보를 읽어와 알맞은 빈 객체를 생성하고 각 빈을 의존 주입하는 작업을 수행합니다. 즉, 스프링 컨테이너를 초기화 합니다.
초기화하면 컨테이너를 사용할 수 있게 됩니다. 컨테이너를 통해 getBean을 사용해서 객체를 가져올 수 있다는 뜻입니다. 이렇게 볼일이 끝나고 나면 컨테이너의 사용을 종료하고 스프링 컨테이너의 라이플 사이클이 완성됩니다.
간단하게 정리하자면 이렇습니다.
- 컨테이너 초기화: 빈 객체의 생성, 의존 주입, 초기화
- 컨테이너 종료: 빈 객체의 소멸
그렇다면 스프링 컨테이너가 관리하는 빈 객체의 라이프 사이클은 어떻게 될까요?
객체 생성 -> 의존 설정 -> 초기화 -> 소멸
빈 객체는 이와 같은 과정의 라이프 사이클을 거치게 됩니다. 그렇다면 이 과정이 어떻게 동작하는지 살펴보겠습니다.
1) 스프링 인터페이스
스프링 컨테이너는 내부적으로 빈 객체를 초기화하고 소멸하기 위해 인터페이스에 구현된 기능을 사용합니다.
InitializingBean: afterPropertiesSet() 메소드 구현해 초기화
DisposableBean: destroy() 메소드 구현해 종료
이 초기화와 소멸과정이 필요한 예로는 DB 커넥션 풀과 채팅 클라이언트가 있습니다.
외부 서버와 연결하고 끊는 과정이 필요하기 때문입니다.
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() 실행");
}
}
Client 클래스에서 InitializingBean, DisposableBean 인터페이스를 상속받아 초기화와 종료를 구현하였습니다.
설정 클래스에서 빈을 생성하고 메인 클래스에서 빈을 호출하면 출력코드가 순서대로 실행되게 됩니다.
2) 커스텀 메소드
위의 두 인터페이스를 상속받아 메소드를 구현하지 않고 직접 메소드를 구현하는 방법도 있습니다.
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 어노테이션을 사용해 속성에 지정해주면 됩니다.
@Configuration
public class AppCtx {
@Bean(initMethod = "connect", destroyMethod = "close")
public Client2 client2() {
Client2 client = new Client2();
client.setHost("host");
return client;
}
}
앞의 포스팅에서 스프링 컨테이너는 싱글톤 타입으로 객체를 한 개만 생성한다고 했습니다.
설정값을 따로 주지 않으면 디폴트가 싱글톤이기 때문입니다.
하지만 프로토타입 범위로 빈을 설정할 수도 있습니다. 바로 빈의 범위(Scope)를 프로토 타입으로 지정하는 것입니다.
@Configuration
public class AppCtxWithPrototype {
@Bean
@Scope("prototype")
public Client client() {
Client client = new Client();
client.setHost("host");
return client;
}
@Bean(initMethod = "connect", destroyMethod = "close")
@Scope("singleton")
public Client2 client2() {
Client2 client = new Client2();
client.setHost("host");
return client;
}
}
설정 클래스에서 client 메소드를 프로토타입, client2 메소드를 싱글톤으로 지정했습니다.
그렇다면 메인 클래스에서 두 메소드를 호출하면 과연 어떻게 될까요?
Client client1 = ctx.getBean(Client.class);
Client client2 = ctx.getBean(Client.class);
System.out.println("client1 == client2 : " + (client1 == client2));
이렇게 출력하면 답은 false가 출력됩니다.
둘 다 싱글톤이었다면 같은 객체로 인식하기 때문에 true가 출력되지만, 프로토타입은 호출할 때마다 매번 새로운 객체를 생성하기 때문에 둘은 다른 객체로 인식됩니다.
프로토타입을 사용할 경우 스프링 컨테이너가 빈 객체 생성과 초기화까지는 작업까지는 수행해 주지만, 컨테이너가 종료되어도 객체의 소멸까지는 이루어지지 않기 때문에 직접 코드에서 소멸처리를 해줘야 합니다.
출처: 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문
'Spring' 카테고리의 다른 글
[Spring] DB 연동 및 쿼리 작성 (JdbcTemplate) (feat. Connection Pool, 커넥션 풀, DataSource, query()) (0) | 2023.09.17 |
---|---|
[Spring] AOP 프로그래밍 (@Aspect, @Pointcut , @Advice) (0) | 2023.09.03 |
[Spring] 컴포넌트 스캔 (@Component, @ComponentScan) (0) | 2023.08.27 |
[Spring] 스프링 의존 자동 주입이란? (@Autowired) (0) | 2023.08.13 |
[Spring] 스프링 DI 설정 방식 (생성자, 세터 방식) (0) | 2023.07.28 |