빈 생명주기 콜백을 알아야 하는 이유와 객체의 초기화 및 스프링 종료에 대한 글은 아래 포스팅을 참고하면 된다.
오늘 포스팅은 위 포스팅의 연장선이라 보면 된다.
저번에도 말했듯이, 스프링이 빈 생명주기 콜백을 해주는 방법은 총 세가지가 있다고 하였다.
- 인터페이스(InitializingBean, DisposableBean)
- 초기화 메소드, 종료 메소드 지정
- @PostConstruct, @PreDestroy 어노테이션 지정
그 중에서 1번은 단점만 있고, 20년 전 방법이므로 따로 적지 않으려고 한다.
난 3번은 내 기억속에 가지고 갈 생각이고, 2번은 필요한 상황이 있을 때마다 이 포스팅을 내가 직접 보면서 기억을 상기시키려 하고, 1번은 정말 쓸일이 없을거라 판단해 아예 포스팅을 하지 않을 생각이다.
2번과 3번 방법을 기록해놓고 1번이 왜 쓰이지 않는지, 단점이 무엇인지에 대해 쓸 생각은 있다.
초기화 메소드, 종료 메소드 지정
아래는 웹서버와 연결하여 request로 주어진 url을 연동하는 코드이다.
package hello.core.lifecycle;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class NetworkClient {
private String url;
public NetworkClient(){
System.out.println("생성자 호출 = "+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();
}
}
package hello.core.lifecycle;
import org.junit.jupiter.api.Test;
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(initMethod="init", destroyMethod="close")
public NetworkClient networkClient(){
NetworkClient networkClient=new NetworkClient();
networkClient.setUrl("https://hello-spring.dev");
return networkClient;
}
}
}
아래와 같이 destroyMethod="close"를 제외해도 괜찮은데, 그 이유는 @Bean의 destroyMethod의 기본값이 (inferred)로 돼있기 때문이다. 따라서 close, shutdown의 종료 이름 메소드가 보인다 싶으면 그 메소드를 자동으로 호출해주기 때문에 아래와 같이 @Bean 어노테이션의 정보를 일부 생략해주어도 괜찮다.
package hello.core.lifecycle;
import org.junit.jupiter.api.Test;
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(initMethod="init")
public NetworkClient networkClient(){
NetworkClient networkClient=new NetworkClient();
networkClient.setUrl("https://hello-spring.dev");
return networkClient;
}
}
}
이 방법의 특징은 아래와 같다.
- 메소드 이름을 자유롭게 지정 가능
- 스프링 빈이 스프링 코드에 의존하지 않는다. (@Bean과 같은 설정정보에 의존하지 코드 자체에 의존하진 않음)
- 설정 정보를 사용하므로 외부 라이브러리리에도 초기화, 종료 메소드 적용 가능
마지막 세 번째 특징 때문에 객체 초기화, 종료 함수 지정해주는 방법이 쓰일 때가 있다고 한다.
@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);
}
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();
}
}
package hello.core.lifecycle;
import org.junit.jupiter.api.Test;
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("https://hello-spring.dev");
return networkClient;
}
}
}
@Bean 설정정보를 따로 적지 않아도 @PostConstruct, @PreDestroy에 의해 알아서 초기화 및 종료가 된 것을 확인할 수 있을 것이다.
이 방법의 특징은 아래와 같다.
- javax 패키지 내장 어노테이션으로 스프링 종속 기술이 아닌, 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 작동한다.
- 어노테이션으로 설정 정보를 지정해주는 컴포넌트 스캔과 잘 어울린다.
- 외부 라이브러리 초기화, 종료 시엔 사용이 불가능하여 이때는 위에서 메소드 지정한 방법인 @Bean의 기능을 사용해야 한다.
인터페이스 방법의 단점
인터페이스 방법은 20년 된 방법이기도 하고, 2번 방법의 장점을 완전 반대로 한 특성을 가진다 보면 된다.
아래는 2번 방법의 장점이다.
- 메소드 이름을 자유롭게 지정 가능
- 스프링 빈이 스프링 코드에 의존하지 않는다. (@Bean과 같은 설정정보에 의존하지 코드 자체에 의존하진 않음)
- 설정 정보를 사용하므로 외부 라이브러리리에도 초기화, 종료 메소드 적용 가능
아래는 1번 방법의 단점이다.
- 인터페이스 오버라이딩 메소드이므로 이름 따로 지정 불가
- 스프링 빈이 스프링 코드에 의존함.
- 코드에 의존하므로 외부 라이브러리리에 초기화, 종료 메소드 적용 불가능
따라서 3번 방법을 주로 이용하다가, 외부 라이브러리에 초기화, 종료 메소드를 적용해야 할 상황이 오면 2번 방법을 사용하면 될 듯하다.
사실 아직은 객체의 초기화 및 종료 설정을 언제 해주어야 할 지 감이 잘 잡히지 않는다.
큰 프로젝트를 진행해보지 않아서 그럴 수 있겠다.
확실한건 지금 내가 필요성을 느끼지 못하고 있으므로 곧 까먹을 게 뻔하여 급하게 내 블로그에 포스팅해놓았다.
'JAVA > Spring_Core' 카테고리의 다른 글
[Spring] 스프링 핵심원리 - 기본편 완강 후기 (0) | 2021.04.10 |
---|---|
[Spring] 빈 생명주기 콜백과 객체의 초기화 (2) | 2021.04.01 |
lombok은 진짜 신세계다... (0) | 2021.03.22 |
[Spring] 스프링 기능들, 특히 컴포넌트 스캔 공부하면서 (0) | 2021.03.11 |
스프링 공부하면서 느낀 점 (0) | 2021.03.11 |