[Spring] Bean LifeCycle 이란 무엇일까?
빈 생명주기 콜백(Bean LifeCycle)
데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요합니다.
스프링 빈도 위와 같은 원리로 초기화 작업
과 종료 작업
이 나눠서 진행됩니다. 간단하게 말하면 객체 생성 -> 의존관계 주입
이라는 라이프사이클을 가집니다.
즉, 스프링 빈은 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료됩니다.
Spring 의존관계 주입 과정
가장 처음에는Spring IoC 컨테이너
가 먼저 만들어지는 과정이 일어납니다. 위의 그림은 Spring Boot
에서 Component-Scan
으로 Bean 등록을 시작하는 과정의 그림으로 표현하였습니다.
그리고 위와 같이 @Configuration
, @Controller
, @Service
등등 Bean 으로 등록할 수 있는 어노테이션들과 설정파일들을 읽어 IoC 컨테이너 안에 Bean 으로 등록을 시킵니다.
그리고 의존 관계를 주입하기 전
의 준비
단계가 존재합니다. 이 단계에서 객체의 생성이 일어납니다. 여기서 한가지 알고 넘어가야 할 부분이 있습니다.
생성자 주입
: 객체의 생성, 의존관계 주입이 동시에 일어남setter, Field 주입
: 객체의 생성 -> 의존관계 주입으로 라이프 사이클이 나누어져 있음
즉, 생성자 주입
은 위의 단계와 아래의 단계가 동시에 진행된다는 뜻입니다. 왜 생성자 주입은 동시에 일어나는 것일까요?
public class Car {
private Tire tire;
public Car(Tire tire) {
this.tire = tire;
}
}
이번에도.. Car
, Tire
코드를 보겠습니다. 위에는 생성자 주입을 사용하고 있습니다. 스프링에서 의존성 주입을 할 때 어떤 마법을 부려서 의존성을 주입해주지 않습니다. 자바 코드와 같이 new
연산을 사용해서 주입을 해줍니다.
자바 코드에서 new
연산을 호출하면 어떻게 될까요? 생성자가 호출이 됩니다.
그런데.. Car 클래스에 존재하는 Tire 클래스와의 의존관계가 존재하지 않는다면 Car 클래스는 객체 생성이 가능할까요? 객체 생성이 불가능합니다. 그렇기 때문에 생성자 주입
에서는 객체 생성, 의존관계 주입
이 하나의 단계에서 일어나는 것입니다.
public class Car {
private Tire tire;
public setTire(Tire tire) {
this.tire = tire;
}
}
이번에는 setter 주입의 경우를 보겠습니다. 위의 상황에서 Car
객체를 만들 때 의존 관계가 필요할까요? 필요하지 않습니다. 즉, Car 객체를 만들 때 Tire 객체와의 의존 관계가 없어도 Car 객체는 만들 수 있는 것입니다.
따라서 객채 생성 -> 의존 관계 주입
의 단계로 나누어서 Bean LifeCycle
이 진행됩니다.
그래서 위와 같이 코드에 작성한 의존관계를 보고 IoC 컨테이너에서 의존성 주입을 해줍니다.
스프링 의존관계 주입이 완료된 시점을 개발자가 어떻게 알 수 있을까?
Bean으로 등록된 객체들을 초기화
를 하고 싶다면 어떻게 할 수 있을까요? 의존관계가 주입되기 전에 아무 때나 하면... 에러가 발생하기 때문에 의존관계 주입이 된 후에 초기화 작업
을 해야합니다. 그러면 초기화하는 단계를 어떻게 알 수 있을까요?
먼저 스프링의 Bean LifeCycle
을 보면 아래와 같습니다.
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료
스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메소드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공합니다.
또한 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백
을 제공합니다.
초기화 콜백
: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출소멸전 콜백
: 빈이 소멸되기 직전에 호출
객체의 생성과 초기화를 분리하자.
생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면에 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는등 무거운 동작을 수행한다.
따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다는 객체를 생성하는 부분과 초기화 하는 부분을 명확하게 나누는 것이 유지보수 관점에서 좋다.
물론 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우에는 생성자에서 한번에 다 처리하는게 더 나을 수 있다.
스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원합니다.
인터페이스(InitializingBean, DisposableBean)
설정 정보에 초기화 메소드, 종료 메소드 지정
@PostConstruct, @PreDestroy 애노테이션 지원
먼저 InitializingBean, DisposableBean
인터페이스의 콜백을 이용한 코드를 알아보겠습니다.
@Component
public class TestComponent implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception { // 의존관계 주입이 끝나면 호출해주는 콜백
System.out.println("초기화 콜백 테스트");
}
@Override
public void destroy() throws Exception {
System.out.println("소멸전 콜백 테스트");
}
}
InitializingBean
은afterPropertiesSet
메소드로초기화
를 지원합니다.(의존관계 주입이 끝난 후에 초기화 진행)DisposableBean
는destroy
메소드로소멸
을 지원합니다. (Bean 종료 전에 마무리 작업 예를들면 close()와 같은.. 것들?)
초기화, 소멸 인터페이스 단점
- InitializingBean, DisposableBean 인터페이스는 스프링 전용 인터페이스 입니다. 해당 코드가 스프링 전용 인터페이스에 의존합니다.
- 초기화, 소멸 메서드의 이름을 변경할 수 없습니다.
- 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없습니다.
InitializingBean, DisposableBean 인터페이스를 사용하는 초기화 및 종료 방법은 스프링 초창기에 나온 방법들이기 때문에 지금은 거의 사용하지 않는다.
@PostConstruct, @PreDestory 어노테이션
- 최신 스프링에서 가장 권장하는 방법입니다.
- 애노테이션 하나만 붙이면 되므로 매우 편리합니다.
- 패키지를 잘 보면 javax.annotation.PostConstruct 입니다. 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준입니다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작합니다.
- 컴포넌트 스캔과 잘 어울립니다.
- 유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것입니다. 외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 기능을 사용하면 됩니다.
@Component
public class TestComponent {
@PostConstruct
public void initTest() {
System.out.println("초기화");
}
@PreDestroy
public void destoryTest() {
System.out.println("종료");
}
}
정리하기
@PostConstruct, @PreDestory 애노테이션을 사용하자
코드를 고칠 수 없는 외부 라이브러리를 초기화, 종료해야 하면 @Bean 의 initMethod , destroyMethod 를 사용하자.