Conclusion
@Postonstruct, @PreDestory annotation 으로 콜백메소드를 지정하라.
외부 라이브러리 콜백 메소드 지정이 필요할 때만 @Bean 메소드 지정 방식을 사용하라.
Why to use?
애플리케이션을 개발하다보면 초기에 DB 커넥션 풀이나 Socket 등 을 미리 열어두는 것이 필요할 수 있다.
반대로 이렇게 연결을 유지하는 객체들은 애플리케이션 종료시 안전하게 연결 해제 (혹은 반납)을 해줄 필요가 있다.
이를 'release' 혹은 'close' 라 하는데, Spring은 이 동작을 안정적이고 편하게 할 수 있는 매커니즘을 제공한다.
스프링 빈은 객체 생성 -> 의존 관계 주입
초기화 작업은 의존관계 주입 '후'에 되야한다. 이 "의존 관계 주입 완료" 시점을 어떻게 알 수 있는가?
콜백 메서드를 통해 초기화 시점을 알려준다.
Spring Container 종료 직전에 callback 을 준다.
Spring Bean Lifecycle
스프링 컨테이너 생성 -> 빈 생성 -> DI -> 초기화 콜백 -> Spring Bean 사용 -> 소멸전 콜백 -> 스프링 컨테이너 종료
객체의 생성과
초기화를 분리해라.
SOLID 원칙중 SRP (Single Responsible Principal) 을 돌아보라.
생성자는 필수 정보를 받고 메모리를 할당하여 객체를 생성하는 책임을 가졌다.
반면, 초기화는 외부 커넥션 연결 등의 무거운 동작을 수행한다.
생성자 안에서 무거운 초기화 작업을 함께하기보다, 객체 생성부와 초기화를 나누는 것이 유지보수 관점에서 좋다.
초기화 작업이 내부 값 변경 정도로 단순한 경우에는 생성자에서 한번에 처리하는게 더 나을 수 있다.
How to use
(초기화를 위한) 빈 생명주기 콜백 3가지
- 인터페이스 (InitializeBean, DisposableBean)
(1) 인터페이스 방식 (InitializeBean, DisposableBean)
현재는 거의 사용하지 않는다.
- 스프링 전용 인터페이스로 스프링에 지나치게 의존적임.
- 외부 라이브러리에 적용이 어렵다.
NetworkClient.java
인터페이스 방식으로 네트워크 연결 객체를 생성/해제할 때
InitializeBean, DisposableBean 인터페이스의 메소드(`afterPropertiesSet()`, `destroy()`)를 구현하여 콜백 단계를 조정한다.
public class NetworkClient implements InitializingBean, DisposableBean {
private String url;
// 애플리케이션 시작시 특정 URL 에 연결
public NetworkClient() {
System.out.println("생성자 호출, url = " + url);
}
public void setUrl(String url) {
this.url = url;
}
public void connect() {
System.out.println("Connected to : " + url);
}
public void call(String message) {
System.out.println("call = " + url + " message = " + message);
}
// 애플리케이션 종료 전 호출
public void disconnect() {
System.out.println("Connection closed of " + url);
}
// 의존관계 주입이 끝나면 실행하겠다는 뜻
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("After bean created and DI");
connect();
call("Initialize connection");
}
@Override
public void destroy() throws Exception {
System.out.println("Before close() method be called, call `destroy()` callback method.");
disconnect();
}
}
BeanLifeCycle.java
public class BeanLifeCycleTest {
@Test
public void lifeCycleTest() {
// Spring Bean 생성 및 DI 작업 후
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
// 아래 라인 실행 전에 `afterPropertiesSet()` 이 먼저 실행된다.
var networkClient = ac.getBean(NetworkClient.class);
// 아래 라인 실행 전에 `disconnect()` 이 실행된다.
ac.close();
}
@Configuration
static class LifeCycleConfig {
@Bean
public NetworkClient networkClient() {
var networkClient = new NetworkClient();
networkClient.setUrl("https://hello-spring.dev");
return networkClient;
}
}
}
(2) @Bean 메소드명 지정
- 스프링 빈이 스프링에 의존적이지 않음.
- 메소드 이름이 자유로움 (특정 인터페이스 메소드를 오버라이딩 하는 방식이 아님)
- 코드가 아니라 설정 정보를 사용하므로 외부 라이브러리에도 초기화/종료 메소드 적용 가능.
일반적으로 외부 라이브러리는 `close()`, `shutdown()` 이름의 종료 메소드를 사용한다.
@Bean 어노테이션에는 추론 매커니즘이 존재하기 때문에 명시적으로 종료 메소드명을 지정하지 않은 경우에도
`close(), `shutdown()` 메소드 명을 자동으로 찾아 호출해준다.
사용하지 않고싶다면 `destroyMethod=""` 을 사용한다.
NetworkClient.java
// 초기화시 실행될 메소드
public void init() {
System.out.println("After bean created and DI");
connect();
call("Initialize connection");
}
// 컨테이너 종료 직전에 실행될 메소드
public void close() {
System.out.println("Before close() method be called, call `destroy()` callback method.");
disconnect();
}
BeanLifecycleTest.java
콜백 메소드 명을 @Bean annotation 에 명시하기만 하면된다.
// ..
@Configuration
static class LifeCycleConfig {
// @Bean annotation 으로 메소드 이름만 지정하면 된다.
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
var networkClient = new NetworkClient();
networkClient.setUrl("https://hello-spring.dev");
return networkClient;
}
}
// ..
(3) Annotation 방식 @PostConstruct, @PreDestroy
얘가 국룰이다.
- 최신 Spring 이 권장하는 방식이자 JRE 표준방식
- Easy to use
- Component Scan 과 궁합이 좋다.
- But, 외부 라이브러리에 적용이 어렵다.
=> 외부 라이브러리 초기화/종료 콜백 메소드 지정이 필요할 땐 (2) @Bean 메소드명 지정방식을 사용하자.
NetworkClient.java
@PostConstruct
public void init() {
System.out.println("After bean created and DI");
connect();
call("Initialize connection");
}
@PreDestroy
public void close() {
System.out.println("Before close() method be called, call `destroy()` callback method.");
disconnect();
}
BeanLifecycleTest.java
@Configuration
static class LifeCycleConfig {
@Bean
public NetworkClient networkClient() {
var networkClient = new NetworkClient();
networkClient.setUrl("https://hello-spring.dev");
return networkClient;
}
}
'JVM > Spring' 카테고리의 다른 글
[SpringBoot] Naming methods in each layer (0) | 2023.02.09 |
---|---|
[Spring] Library 살펴보기 및 스프링 환경설정 (0) | 2022.12.12 |
[Spring] Bean Scope (0) | 2022.12.07 |
[Spring] Autowired 사용시 주의 (Feat. Custom Annotation) (0) | 2022.12.01 |
[Spring] Component Scan, DI autowired (0) | 2022.11.30 |