728x90
1. 빈 생명주기 콜백 시작
- Spring bean 생성, 종료 직전 @Bean 객체 내부 method를 호출해주는 기능
- 생성후 초기화시 호출
- 죽기 직전 안전 종료 메서드 호출
- 필요한 이유
- db connection pool, network socket처럼 app시작 시점에 필요한 연결을 미리 해둘 경우
- app 종료 시점에 연결을 모두 종료하는 작업을 진행하기 위해서 객체 내부 종료 작업 필요
1.1. 예제
- 문제 코드 - 미리 connect()를 하고 싶어서 생성자 내부에 connect() 넣어봤다 - 시도를 한 것. 잘못된 시도
package hello.core.lifecycle;
public class NetworkClient {
private String url;
// 생성자 내부에 connect() 메서드 호출 및 call() 메서드를 호출
public NetworkClient(){
System.out.println("생성자 호출, url = " + url);
connect();
call("초기화 연결 메시지")
}
public void setUrl(String url){ this.url = url; }
public void connect(){ System.out.println("connnect: " + url); }
public void call(String message){ System.out.println("call: " + url + ", message = " + message); }
// 해당 bean 종료 직전 호출 메서드
public void disconnect(){
System.out.println("close: " + url);
}
}
- test 코드
package hello.core.lifecycle;
@Test
public void lifeCycleTest(){
// spring container에 bean 등록
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
NetworkClient client = ac.getBean(NetworkClient.class); // spring 시작
ac.close(); // spring container 종료 method
}
// 설정 파일
@Configuration
Static class LifeCycleConfig{
@Bean
public NetworkClient networkClient(){
// bean 등록 원하는 객체 생성
NetworkClient networkClient = new NetworkClient();
// setter 의존관계 주입
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
- console 결과
생성자 호출, url = null
connect: null
call: null message = 초기화 연결 메시지
- 해당 결과 원인
- 객체 생성시 url이 없지만 생성자 block 내부에 url 호출 코드가 존재해서 null인 상태로 method() 수행
- 객체를 생성한 후 외부에서 url을 setter로 주입한다. - DI는 완료 되었지만 미리 연결 되진 않음
- 이후 외부에서 url connect()를 호출 시, 그때 작동
- 현 문제코드에서 어떻게든 해결하려는 code
public class NetworkClient {
private String url;
// 굳이 초기화 단계에서 connect하려고 할 경우
public NetworkClient(){
url = "url 객체 정보 넣어주기"; // 생성 초기화 분리 안되는 경우
System.out.println("생성자 호출, url = " + url);
// connect(), call()과 같이 객체 생성이 아닌 뭔갈 수행하는 부분이 생성자랑 같이 있으면 유지보수에 안좋다.
connect();
call("초기화 연결 메시지")
}
public void connect(){ System.out.println("connnect: " + url); }
}
- 문제점
- 생성과 초기화 단계 분리가 되어있지 않다.
- 현재 방식은 생성자 내부에 setUrl 값을 넣어서 수행하려는 시도와 동일
- 유지 보수에 좋지않다.
- 생성자는 해당 instance 생성에 집중해야하는데 url과 같이 외부 작업에도 집중하는 문제점 발생
- 정리
- Network class
- 생성자 존재
- connection()
- disconnect()
- 등등 여러 method 존재
- @Configuration class
- network.class를 빈으로 등록
- url bean과 의존 관계 주입
- 이후 초기화 call back으로 connect() 수행 == 외부 호출 전 미리 connect() 하려는 동작
<- 이렇게 하고 싶지만 현재는 network.class에 어떻게 해야할지 모른다. - 소멸전 call back으로 disconnect()를 하고 싶은 것
- 이전 까지 했던 작동 방법
- network.class를 빈으로 등록
- url bean과 의존 관계 주입
- 그리고 외부 호출이 오면 그 때 connect()를 수행
- Network class
- 목표
- "빈 생성 - 의존 관계 주입" 후에 미리 connect()와 같은 작업을 외부 요청 없이 수행을 원한다.
- 생성자 주입이라고 해도 수정자 주입과 현 문제점이 다를게 없다. 얘도 미리 connect()할 방법이 없다.
1.2. 스프링 빈 라이프사이클 (중요)
- 예외적으로 생성자 주입은 빈 생성과 의존관계 주입이 동시에 일어난다. - 자동, 수동 상관없이 동일
- 수동(AppConfig) 같은 경우도 생성자 주입, setter 주입 다 가능하다.
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 소멸전 콜백 -> 스프링 종료
# 초기화 콜백 : DI까지 끝내고 외부 호출 전 미리 수행하고 싶은 method 수행
# 소멸전 콜백 : spring 종료 직전 해당 작업이 먼저 안전히 종료하고 싶은 method() 수행
2. 스프링 콜백 3가지 방법
- 인터페이스 - 안씀
- @Bean - 3.번으로 해결 안될 경우 사용
- @PostConstruct @Predestroy - 제일 많이 사용
2.1. 인터페이스(InitializingBean, DisposableBean )
- 단점
- 스프링 전용 인터페이스로 초기화, 소멸 메서드의 이름을 변경 불가
- 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.
- 작동 방식
- InitiallizingBean과 DisposableBean 인터페이스의 afterPropertiesSet(), destroy() 메서드를 이용해서 DI 후 초기화 콜백 시작
- afterPropertiesSet() - DI 끝난 후 초기화 값을 넣어주는 과정
- destroy()로 spring bean 소멸 지원한다.
- InitiallizingBean과 DisposableBean 인터페이스의 afterPropertiesSet(), destroy() 메서드를 이용해서 DI 후 초기화 콜백 시작
// public class NetworkClient {
public class NetworkClient implements InitiallizingBean, DisposableBean {
private String url;
// 의존관계 주입
public void setUrl(String url){ this.url = url; }
public NetworkClient(){
System.out.println("생성자 호출, url = " + url);
// connect(); call(" 초기화 연결 메시지 ");
}
@Override // InitiallizingBean method
public void afterPropertiesSet() throws Exception{
System.out.println("DI까지 끝나면 호출한다는 method - InitiallizingBean");
connect();
call("초기화 연결 메시지");
}
@Override // DisposableBean method
public void destroy() throws Exceptioin{
System.out.println("spring container 작업이 종료되면 소멸시키는 method - DisposableBean");
disConnect();
}
public void connect(){ System.out.println("connnect: " + url); }
public void call(String message){ System.out.println("call: " + url + ", message = " + message); }
public void disconnect(){ System.out.println("close: " + url); }
}
2.2. 빈 등록 초기화, 소멸 메서드 지정
- 설정 정보에 @Bean(initMethod = "init", destroyMethod = "close")처럼 초기화, 소멸 메서드를 지정
- 설정 정보 사용 특징
- 메서드 이름을 자유 사용 가능 - init하든 initial 하든 상관 없다.
- 스프링 빈이 스프링 코드에 의존하지 않는다. = 나의 source code에 스프링의 인터페이스 같은 걸 넣지 않음.
- 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드 적용 가능
- 추가 기능 - 종료 메서드 추론 (autocloseable 학습)
- 참고 : 라이브러리는 대부분 close(), shutdown() 이라는 이름의 종료 메서드를 사용한다.
- @Bean의 destroyMethod 속성에는 default로 (inferred) 이 등록되어 있다. - 추론 기능
- 추론 기능은 close(), shutdown() 라는 이름의 메서드를 자동으로 종료시점 호출
-> 외부 라이브러리는 대부분 close, shutdown 이름의 종료 메서드를 사용 - 따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 잘 동작
== 따로 @Bean(destroyMethod= "XXX")를 작성하지 않아도 동작한다. - 내 애플리케이션을 엉망으로하고싶은데하면 -> destroyMethod="" 처럼 빈 공백을 지정 (추론 기능을 사용안하는 방법)
- @Bean의 destroyMethod 속성에는 default로 (inferred) 이 등록되어 있다. - 추론 기능
// public class NetworkClient implements InitiallizingBean, DisposableBean {
public class NetworkClient {
private String url;
public NetworkClient(){
System.out.println("생성자 호출, url = " + url);
}
// 의존관계 주입
public void setUrl(String url){
this.url = url;
}
// 초기화 콜백 하고 싶은 method를 init() 메서드 내부에 넣기
// -> 설정 파일 bean에서 초기화 콜백 method 명시
public void init(){
System.out.println("DI까지 끝나면 호출한다는 method - InitiallizingBean");
connect(); // 초기화 콜백 하고 싶은 method
call("초기화 연결 메시지"); // 초기화 콜백 하고 싶은 method
}
// 소멸전 콜백하고 싶은 method를 close() 메서드 내부에 넣기
public void close(){
System.out.println("spring container 작업이 종료되면 소멸시키는 method - DisposableBean");
disConnect(); // 소멸전 콜백 하고픈 method
}
...
}
}
// 설정 파일
@Configuration
Static class LifeCycleConfig{
// 설정 파일 bean에서 초기화 콜백, 종료 콜백 method 명시
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
2.3. 애노테이션 @PostConstruct, @PreDestroy
- 제일 많이 사용
- spring에서 권장하는 방법
- @PostConstruct, @PreDestroy 애노테이션 특징
- 최신 스프링에서 가장 권장하는 방법
- 애노테이션 하나만 붙이면 되므로 매우 편리하다.
- 패키지는 javax.annotation.PostConstruct 라는 스프링에 종속적인 기술이 아니라 JSR-250라는 자바 표준이다.
- 스프링이 아닌 다른 컨테이너에서도 동작한다. 컴포넌트 스캔과 잘 어울린다.
- 단점
- 외부 라이브러리에는 적용하지 못한다.
- 외부 라이브러리를 초기화, 종료 해야 하면 @Bean(initMethod, destroyMethod)의 기능을 사용
public class NetworkClient {
private String url;
public void setUrl(String url){ this.url = url; }
public NetworkClient(){ System.out.println("생성자 호출, url = " + url); }
// 초기화 콜백
@PostConstruct
public void init(){
System.out.println("DI까지 끝나면 호출한다는 method - InitiallizingBean");
connect();
call("초기화 연결 메시지");
}
// 소멸전 콜백
@PreDestroy
public void close(){
System.out.println("spring container 작업이 종료되면 소멸시키는 method - DisposableBean");
disConnect();
}
// 서비스 시작 ~ 서비스 종료 까지의 코드
public void connect(){ System.out.println("connnect: " + url); }
public void call(String message){ System.out.println("call: " + url + ", message = " + message); }
public void disconnect(){ System.out.println("close: " + url); }
}
// TEST
@Configuration
Static class LifeCycleConfig{
@Bean
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}
}
3. 외부 라이브러리
- 오픈소스들을 외부 라이브러리라고 이해
- 이 경우 소스코드를 포함하는것이 아니라 이미 컴파일된 class 파일이 모여있는 jar 파일을 포함
- 따라서 소스코드 수정이 불가능
- 특정 객체(NetworkClient)로 init, destory를 wrapping 하지 않고 외부 라이브러리로부터 객체를 생성해야 하는 경우
- 코드 수정이 어려우므로 빈 설정을 통해 진행
- --> 코드 수정이 어렵다는 것이 외부라이브러리 클래스 자체에 @PostConstruct, @PreDestroy 를 못 넣는다는 의미
@Configuration
public class BeanConfig {
@Bean(initMethod = "externalLibraryInit", destoryMethod = "externalLibraryDestory")
public ExternalLibraryObject externalLibraryObject() {
return new ExternalLibraryObject();
}
이전 발행글 : 스프링 핵심 원리 이해 6 - 의존관계 자동 주입
다음 발행글 : 스프링 핵심 원리 이해 8 - bean scope
출처: 인프런 스프링 핵심 원리 - 기본편