[Spring] 빈 생명주기

Seongho·2023년 2월 4일
1

Spring

목록 보기
11/13
post-thumbnail

빈 객체의 초기화와 종료

데이터베이스 커넥션 풀이나 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고 종료 시점에 연결을 끊는 작업을 수행하려면 빈 객체의 초기화와 종료가 필요하다.

** DB connection pool : 웹 애플리케이션 서버와 데이터베이스를 연결하는 것에 너무 많은 비용이 들어서 데이터베이스와의 커넥션을 미리 만들어놓고 pool에 저장한 후, 필요할 때마다 꺼내서 사용하고 반환하는 방법

예제

위부 네트워크와 연결하는 빈 객체를 생성한다고 생각하면, 빈 객체 초기화는 "외부 네트워크와 연결"이고 빈 객체 종료는 "외부 네트워크와 연결 끊음" 이다.

public class NetworkClient {
 	private String url;
 //
 	public NetworkClient() {
 		System.out.println("생성자 호출, url = " + url);
 		connect();
 		call("초기화 연결 메시지");
 	}
 //
 	public void setUrl(String url) {
 		this.url = url;
 	}
//    
 	//서비스 시작시 호출
 	public void connect() {
 		System.out.println("connect: " + url);
 	}
 //
 	public void call(String message) {
 		System.out.println("call: " + url + " message = " + message);
 	}
//    
 	//서비스 종료시 호출
 	public void disconnect() {
 		System.out.println("close: " + url);
 	}
}
public class BeanLifeCycleTest {
 	@Test
 	public void lifeCycleTest() {
 		ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
 		NetworkClient client = ac.getBean(NetworkClient.class);
 		ac.close(); 			//스프링 컨테이너를 종료, ConfigurableApplicationContext 필요
 	}
 //
 	@Configuration
 	static class LifeCycleConfig {
 		@Bean
 		public NetworkClient networkClient() {
 		NetworkClient networkClient = new NetworkClient();		//생성자 호출
 		networkClient.setUrl("http://hello-spring.dev");		//url 주입
 		return networkClient;
 		}
 	}
}


생성자를 호출한 후에 connect()를 하고 setUrl()을 통해 url을 주입하기 때문에 당연히 네트워크 연결이 되지 않는다.
생성자에 파라미터로 url을 전달해주는 방법도 있겠지만, 이는 단일 책임 원칙에 어긋난다. 빈 생성, 초기화는 각각의 다른 책임이다. 빈 객체의 초기화는 스프링 빈이 생성되고 의존관계 주입이 모두 끝난 후 실행되어야 한다.
그렇다면 개발자는 스프링 빈이 생성되고 의존관계 주입까지 끝난 그 시점을 알아야 초기화를 진행할 수 있다.

스프링 빈 라이프사이클

스프링은 의존관계 주입이 완료되면 콜백 매서드를 통해서 완료 시점을 알려주고 스프링 컨테이너 종료 전에는 소멸 콜백 매서드를 통해 종료 시점을 알려준다.

  • 스프링 빈의 이벤트 라이프사이클
    스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료
    ++ 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
    ++ 소멸전 콜백: 빈이 소멸되기 직전에 호출

빈 생명주기 콜백 이용 방법

빈 등록 초기화, 소멸 메서드 지정

public class NetworkClient {
 	private String url;
 //
 	public NetworkClient() {
 		System.out.println("생성자 호출, url = " + url);
 	}
 //
 	public void setUrl(String url) {
 		this.url = url;
 	}
//    
 	//서비스 시작시 호출
 	public void connect() {
 		System.out.println("connect: " + url);
 	}
 //
 	public void call(String message) {
 		System.out.println("call: " + url + " message = " + message);
 	}
//    
 	//서비스 종료시 호출
 	public void disconnect() {
 		System.out.println("close: " + url);
 	}
//
    public void init() {
 		System.out.println("NetworkClient.init");
 		connect();
 		call("초기화 연결 메시지");
 	}
//
 	public void close() {
 		System.out.println("NetworkClient.close");
 		disConnect();
 	}
}
public class BeanLifeCycleTest {
 	@Test
 	public void lifeCycleTest() {
 		ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
 		NetworkClient client = ac.getBean(NetworkClient.class);
 		ac.close(); 			//스프링 컨테이너를 종료, ConfigurableApplicationContext 필요
 	}
 //
 	@Configuration
 	static class LifeCycleConfig {
 		@Bean(initMethod = "init", destroyMethod = "close")		//매서드 지정!!!!!!!!!!!!!!
 		public NetworkClient networkClient() {
 		NetworkClient networkClient = new NetworkClient();		//생성자 호출
 		networkClient.setUrl("http://hello-spring.dev");		//url 주입
 		return networkClient;
 		}
 	}
}

  • destroyMethod는 생략 가능하다. 보통 라이브러리들은 종료매서드로 close, shutdown의 이름으로 매서드를 사용하는데, 이를 자동으로 호출해주기 때문이다.

@PostConstruct, @PreDestroy 사용 (권장)

public class NetworkClient {
 	private String url;
 //
 	public NetworkClient() {
 		System.out.println("생성자 호출, url = " + url);
 	}
 //
 	public void setUrl(String url) {
 		this.url = url;
 	}
//    
 	//서비스 시작시 호출
 	public void connect() {
 		System.out.println("connect: " + url);
 	}
 //
 	public void call(String message) {
 		System.out.println("call: " + url + " message = " + message);
 	}
//    
 	//서비스 종료시 호출
 	public void disconnect() {
 		System.out.println("close: " + url);
 	}
//
	@PostConstruct
    public void init() {
 		System.out.println("NetworkClient.init");
 		connect();
 		call("초기화 연결 메시지");
 	}
//
	@PreDestory
 	public void close() {
 		System.out.println("NetworkClient.close");
 		disConnect();
 	}
}
public class BeanLifeCycleTest {
 	@Test
 	public void lifeCycleTest() {
 		ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
 		NetworkClient client = ac.getBean(NetworkClient.class);
 		ac.close(); 			//스프링 컨테이너를 종료, ConfigurableApplicationContext 필요
 	}
 //
 	@Configuration
 	static class LifeCycleConfig {
 		@Bean
 		public NetworkClient networkClient() {
 		NetworkClient networkClient = new NetworkClient();		//생성자 호출
 		networkClient.setUrl("http://hello-spring.dev");		//url 주입
 		return networkClient;
 		}
 	}
}

  • 스프링에서 권장하는 방법이다.
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

스프링에 종속적인 기술이 아니라 자바의 기술이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.

  • 하지만, 코드를 고칠 수 없는 외부 라이브러리 빈을 초기화, 종료해야 하면 @Bean의 initMethod, destroyMethod 를 사용해야 한다.
profile
Record What I Learned

0개의 댓글