요약 :
스프링빈 초기화/종료 콜백을 사용하기 위해
- @PostConstruct, @PreDestroy를 쓰자!
- 근데 외부라이브러리는 수정 못하니까(저 애노테이션 못 붙이니까) @Bean(initMethod = "init", destroyMethod = "close") 사용해서 알맞게 지정해서 사용하자.
빈 생명주기 콜백 메서드 ... 초기화/종료 콜백
애플리케이션이 실행 & 종료되면서 객체의 초기화 & 종료 작업이 필요하다.
스프링은 '초기화 콜백'을 통해 그러한 초기화 시점을 알려주고,
'소멸 콜백'을 통해 빈 소멸 직전에 안전하게 종료 작업을 진행할 수 있게 한다.
∨ 스프링 빈의 이벤트 라이프사이클 (singleton일 경우)
↓ 스프링 컨테이너 생성
↓ 스프링 빈 생성
↓ 의존관계 주입
recall) 생성자 주입인 경우 생성&주입 단계가 분리되진 않음
↓ 초기화 콜백 (빈이 생성되고 빈 의존관계 주입 완료 후 호출)
↓ 사용
↓ 소멸전 콜백 (빈이 소멸되기 직전에 호출)
↓ 스프링 종료
cf) 객체의 생성과 초기화는 분리하자.
즉, 생성자 안에 초기화하는 큰 로직을 넣지 말자는 것이다.
단일책임원칙(SRP)관련 얘기다.
생성자는 메모리할당&객체 생성에만 집중하고,
초기화는 비교적 무거운 동작인 외부커넥션 연결 등을 수행하도록 분리하자.
이러한 초기화/소멸 콜백, 즉 빈 생명주기 콜백은 스프링이 지원하는데,
크게 3가지 방법으로 콜백을 지원한다.
∨ 인터페이스(InitializingBean, DisposableBean) : 거의 사용하지 X
∨ 설정 정보에 초기화 메서드, 종료 메서드 지정
∨ @PostConstruct, @PreDestroy 애노테이션 지원 (권장)
→ lifecycle 관련 Test코드를 통해 이 3가지 방법을 공부해보자.
상황 : NetworkClient가 connect()하고 disconnect()하는 메서드를 호출할 시점을 스프링의 콜백을 통해 관리해줄 것이다.
→ Test 코드
그냥 컨테이너에 빈 등록하고 컨테이너 닫아주는 동작의 라이프사이클을 확인할 것이다 ↓
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://~~");
return networkClient;
}
}
cf) ApplicationContext > ConfigurableApplicationContext > AnnotationConfigApplicationContext
∨인터페이스(InitializingBean, DisposableBean) : 스프링 초창기 방식! 거의 사용X
NetworkClient 클래스가 implements InitializingBean, DisposableBean하면
각각 afterPropertiesSet()과 destroy()를 오버라이딩하게 되어있다.
- afterPropertiesSet() : 즉, 생성&주입해 propertiy를 세팅한 후에 할 동작이다.
-destroy() : 빈 소멸 직전에 할 동작이다.
그래서 NetworkClient 클래스를 다음과 같이 작성할 수 있다.
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 {
System.out.println("NetworkClient.afterPropertiesSet");
connect();
call("초기화 연결 메시지");
}
@Override
public void destroy() throws Exception {
System.out.println("NetworkClient.destroy");
disconnect();
}
}
테스트코드 동작 (: networkClient 빈 등록) 과정을 text로 보면 이렇다.
∨ 생성자 호출 단계
: url에 아무 값도 들어오지 않은 상태 (setter 주입이기 때문에 당연)
∨ 생성&DI를 마치고 나서
: 초기화 콜백 afterPropertiesSet()에서 connect(), call() 메서드가 실행되었다.
∨ ac.close()로 인한 빈 소멸 직전에 (Closing contatiner)
: 소멸전 콜백 destroy()에서 close() 메서드가 안전하게 호출되었다.
이러한 인터페이스 스프링 콜백 방식의 단점
∨ 스프링 전용 인터페이스에 의존해서 작성해야 한다.
annotation 수준이 아니라 아예 코드레벨로 가져와야 한다.
∨ 메서드 이름도 그대로 써야 하고... 등등..
∨ 설정 정보에 초기화 메서드, 종료 메서드 지정
그냥 @Bean에 아예 '이건 초기화 메서드다', '이건 종료 메서드다' 지정해주는 방식이다.
@Bean(initMethod = "init", destroyMethod = "close")
● 위 인터페이스 방식과 달리, 콜백 메서드 이름을 자유롭게 지정 가능하다.
- 초기화 메서드 : init()로 지정함
- 소멸전 메서드 : close()로 지정함
● 여기서 보이는 인터페이스 방식과 다른 또다른 장점은,
빈이 스프링 코드에 의존하지 않는다는 것이다.
아래 코드는 빈으로 등록할 클래스 내부에 자유롭게 작성해준 초기화/소멸 콜백 메서드다.
그냥 자바 코드다. 어떠한 스프링 기능에 의존하지 않는다.
public void init() {
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메시지");
}
public void close() {
System.out.println("NetworkClient.close");
disconnect();
}
이렇게 콜백메서드를 자유롭게 작성해준 뒤에는
config(설정정보) 안에 @Bean(initMethod = "init", destroyMethod = "close")만 해주면 된다.
=> 이 빈의 라이프사이클 콜백 메서드를 지정해준 것이다.
@Configuration
static class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://~~");
return networkClient;
}
}
● 구식의 인터페이스 콜백 방식의 큰 단점 중 하나가 '외부 라이브러리를 이용할 경우'이다.
외부 라이브러리의 경우 이용해야 하는 콜백 메서드가 지정되어있기도 하다. 그러나 인터페이스 콜백 방식을 쓰면, 외부 라이브러리는 수정할 수 없기 때문에, 빈으로 등록할 클래스에 콜백 인터페이스를 적용하는 방식의 콜백은 적용할 수 없다.
하지만 이렇게 설정 정보에 @Bean(initMethod = "init", destroyMethod = "close")로 등록해주는 콜백 방식을 사용하면, 외부 라이브러리에도 초기화 & 종료 메서드를 적용할 수 있다.
그냥 @Bean할 때 콜백으로 사용할 메서드명만 적어주면 되니까!!
● 참고)
지정하지 않아도 자동으로 destroyMethod가 호출된다?!
@Bean(initMethod = "init", destroyMethod = "close")처럼 초기화/종료 콜백 메서드를 지정해주었다.
그러나 @Bean의 destroyMethod 속성은 디폴트가 "(inffered)"로 되어있다.
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
implements BeanDefinition, Cloneable {
...
public static final String INFER_METHOD = "(inferred)";
...
}
일반적으로 외부 라이브러리의 종료메서드는 close, shutdown 라는 이름을 가진다.
그래서 딱히 @Bean에 destroyMethod 값을 지정하지 않아도 디폴트가 "(inffered)"로 되어있기 떄문에
종료 메서드를 추론해서 자동으로 호출해줄 수 있다!
∨ @PostConstruct, @PreDestroy 애노테이션 지원 (권장)
이 애노테이션은 스프링이 아니라 java에서 공식으로 지원하는 거라서 다른 프레임워크에서도 사용가능하다.
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
그냥 메서드에 애노테이션 붙이면 끗..
@PostConstruct
public void init() {
System.out.println("NetworkClient.init");
connect();
call("초기화 연결 메시지");
}
@PreDestroy
public void close() {
System.out.println("NetworkClient.close");
disconnect();
}
그럼 @Bean 등록 할 때 알아서 초기화/종료 메서드로 사용된다.
와우~ 너무좋은걸~
단점은 외부라이브러리에 적용 못한다는 거!
당연.. 클래스 열어서 @PostConstruct @PreDestroy 붙여줄 수 없으니..
그래서 외부라이브러리 초기화/종료 콜백 메서드 사용하기 위해서는 두번째 방법인
@Bean(initMethod = "init", destroyMethod = "close") 이용해주면 되겠다!
요약 :
스프링빈 초기화/종료 콜백을 사용하기 위해
- @PostConstruct, @PreDestroy를 쓰자!
- 근데 외부라이브러리는 수정 못하니까(저 애노테이션 못 붙이니까) @Bean(initMethod = "init", destroyMethod = "close") 사용해서 알맞게 지정해서 사용하자.
'web +a' 카테고리의 다른 글
테스트 주도 개발 (TDD) (0) | 2022.07.14 |
---|---|
Bean Scope 개념 & 프로토타입 스코프 (0) | 2022.07.14 |
자동/수동 빈에 대한 올바른 선택 (0) | 2022.07.14 |
annotation 직접 만들기 (@Qualifier 관련) (0) | 2022.07.13 |
@Autowired 조회 빈이 2개 이상일 때 | 문제점 해결 & 해당 타입 빈 모두 보기 (0) | 2022.07.13 |