스프링 빈의 이벤트 라이프사이클은 아래와 같다.
1. 스프링 컨테이너 생성
2. 스프링 빈 생성
3. 의존관계 주입
4. 초기화 콜백
5. 사용
6. 소멸 전 콜백
7. 스프링 종료
여기서 초기화 콜백은 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출되고,
소멸 전 콜백은 빈이 소멸되기 직전에 호출된다.
스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.
- 인터페이스(InitializingBean, DisposableBean)
- 설정 정보에 초기화 메서드, 종료 메서드 지정
- @PostConstruct, @PreDestroy 애노테이션 지원
하나씩 알아보자.
● 인터페이스(InitializingBean, DisposableBean)
테스트 코드를 통해 알아보자.
EX) 간단하게 외부 네트워크에 미리 연결하는 객체를 하나 생성한다고 가정해보자.
실제로 네트워크에 연결하는 것은 아니고, 단순히 문자만 출력하도록 했다.
이 NetworkClient는 애플리케이션 시작 시점에 connect()를 호출해서 연결을 맺어두어야 하고,
애플리케이션이 종료되면 disConnect() 를 호출해서 연결을 끊어야 한다
NetworkClient.java
package hello.core.lifecycle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class NetworkClient implements InitializingBean, DisposableBean {
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);
}
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("초기화 연결 메시지");
}
@Override
public void destroy() throws Exception {
disConnect();
}
}
InitializingBean 은 afterPropertiesSet() 메서드로 초기화를 지원한다.
DisposableBean 은 destroy() 메서드로 소멸을 지원한다
이제 생성자와 소멸자 메소드를 호출하는 테스트 코드를 작성하자.
package hello.core.lifecycle;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class BeanLifeCycleTest {
@Test
public void lifeCycleTest() {
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
NetworkClient client = ac.getBean(NetworkClient.class); // 객체 생성 -> 의존관계 주입(스프링 빈 라이프사이클)
ac.close();
}
@Configuration
static class LifeCycleConfig {
@Bean
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient(); // 객체를 먼저 생성(생성자 내부에 있는 메소드 등)
networkClient.setUrl("http://hello-spring.dev"); // 객체를 먼저 생성한 다음 url을 주입했기에 내부(생성자)에서 실행한 것들은 null값이 나옴
return networkClient;
}
}
}
테스트 실행 결과는
생성자 호출, url = null
NetworkClient.afterPropertiesSet
connect: http://hello-spring.dev
call: http://hello-spring.dev message = 초기화 연결 메시지
13:24:49.043 [main] DEBUG
org.springframework.context.annotation.AnnotationConfigApplicationContext -
Closing NetworkClient.destroy
close + http://hello-spring.dev
출력 결과를 보면
초기화 메서드가 주입 완료 후에 적절하게 호출된 것을 확인할 수 있다.
그리고 스프링 컨테이너의 종료가 호출되자 소멸 메서드가 호출된 것도 확인할 수 있다.
하지만 위에서 사용한 방법에는 몇가지 단점이 있다.
이 인터페이스는 스프링 전용 인터페이스여서
해당 코드가 스프링 전용 인터페이스에 의존한다.
또 초기화, 소멸 메서드의 이름을 변경할 수 없다.
추가로 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.
● 빈 등록 초기화, 소멸 메서드 지정
설정 정보에 @Bean(initMethod = "init", destroyMethod = "close")처럼 초기화,
소멸 메서드를 지정할 수 있다
예시 코드를 보자.
package hello.core.lifecycle;
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();
}
}
이제 작성했던 테스트 코드에서
@Bean에 아래와 같이 입력해준다.
@Configuration
static class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
테스트 결과는 위와 같이 정상적으로 작동한다.
설정 정보 사용 특징
- 메서드 이름을 자유롭게 줄 수 있다.
- 스프링 빈이 스프링 코드에 의존하지 않는다.
- 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도
초기화, 종료 메서드를 적용할 수 있다.
● @PostConstruct, @PreDestroy 애노테이션 지원
이 방법은 매우 간단하다.
생성자, 소멸자 메서드에 @PostConstruct, @PreDestroy 을 선언해주면 된다.
코드를 살펴보자
package hello.core.lifecycle;
import javax.annotation.PostConstruct;
import javax.annotation.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("초기화 연결 메시지");
}
@PreDestroy // 빈이 소멸되기 전에
public void close() { // 빈이 사용된 후 종료될 때 호출
System.out.println("NetworkClient.close");
disconnect();
}
}
@PostConstruct , @PreDestroy 이 두 애노테이션을 사용하면
편리하게 초기화와 종료를 실행할 수 있다
@PostConstruct, @PreDestroy 패키지를 잘 보면 javax.annotation.PostConstruct이다.
스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다.
따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다.
외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 기능을 사용하자
'Java > Spring' 카테고리의 다른 글
[Spring] 프로토타입 스코프(Prototype Scope) | 빈 스코프 (0) | 2022.01.28 |
---|---|
[Spring] 빈 스코프란? | 싱글톤(singleton) | 프로토타입(prototype) | 웹 관련 스코프 (0) | 2022.01.28 |
[IntelliJ] 인텔리제이 롬복(lombok) 설치 및 설정 | lombok으로 의존관계 설정하기 (0) | 2022.01.24 |
[Spring] 의존관계 주입 방법(@Autowired) | 생성자 주입 | 수정자 주입(setter 주입) | 필드 주입 | 일반 메서드 주입 (0) | 2022.01.24 |
[Spring] 스프링 필터(Spring Filter) | FilterType 옵션 (0) | 2022.01.22 |