[Spring] 빈(Bean) 라이프사이클과 범위
2023. 8. 27.
반응형

이 포스팅은 스프링 컨테이너의 라이프사이클에 대해 다룹니다.

 

스프링 컨테이너는 초기화~종료 라는 라이프 사이클을 가집니다.

 

 

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 프로그래밍 입문

 

 

반응형
myoskin