DB 커넥션 풀이나 네트워크 소켓처럼 애플리케이션 시작 시점에 연결을 미리 해두고, 종료시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다.
스프링 빈은 간단하게 다음과 같은 라이프 사이클을 가진다.
객체 생성 -> 의존 관계 주입
스프링 빈의 이벤트 라이프사이클
스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸전 콜백 → 스프링 종료
"초기화 콜백" : 빈이 생성되고 빈의 의존관계 주입이 완료된 후 호출
"소멸전 콜백" : 빈이 소멸되기 직전에 호출
참고! : 객체의 생성과 초기화를 분리하자.
생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면에 초기화는 이렇게 생성된 값을 활용해서 외부 커넥션을 연결하는 등 무거운 동작을 수행한다.따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다는 객체를 생성하는 부분과 초기화 하는 부분을 명확하게 나누는 것이 유지 보수 관점에서 좋다.
물론 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우에는 생성자에서 한번에 다 처리하는게 좋다.
스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.
@PostConstruct
, @PreDestroy
애노테이션 지원implements InitializingBean, DisposableBean
구현한다. InitializingBean
은 afterPropertiesSet()
메서드로 초기화를 지원한다. DisposableBean
은 destroy()
메서드로 소멸을 지원한다.Spring에서 제공하는 의존관계 주입 후 호출해주고, 스프링 컨테이너가 소멸하기 직전 호출해주는 메서드 제공.
초기화, 소멸 인터페이스 단점
스프링 초창기에 나온 방법이므로 지금은 거의 사용하지 않는다.
설정 정보에 @Bean(initMethod = "init", destroyMethod = "close"
처럼 빈 등록시 내부의 메서드로 초기화, 소멸 메서드를 지정할 수 있다.
@Configuration
static class LifeCycleConfig{
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("<http://hello-spring.dev>");
return networkClient;
}
}
Configuration
에 @Bean
어노테이션에 initMethod
이름, destoryMethod
이름을 주면 해당 Bean
에 Method
이름으로 알아서 호출해준다.
설정 정보 사용 특징
종료 메서드 추론
@Bean
의 destroyMethod
속성에는 아주 특별한 기능이 있다.@Bean
의 destroyMethod
는 기본값이 (inferred)
(추론)으로 등록되어 있다.destroyMethod=""
처럼 빈 공백을 지정하면 된다.@PostConstruct, @PreDestroy 애노테이션 특징
javax.annotation.PostConstruct
이다. 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.최근은 @PostConstruct,@PreDestroy을 가장 권장!!
Spring Container의 Scope
@Scope("prototype")
@Component
public class Bean {}
@Scope("prototype")
@Bean
public Bean bean() {}
싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스를 반환한다. 반면 프로토타입 스코프를 스프링 컨테이너에 조회하면 항상 새로운 인스턴스를 생성해서 반환한다.
처음 스프링 컨테이너에 요청시 이 시점에 프로토타입 빈을 생성하며 필요한 의존 관계를 주입하며, 생성한 프로토타입 빈을 클라이언트에게 반환한다.
이후 스프링 컨테이너에서 프로토 타입빈을 다시 조회하더라도 항상 새로운 인스턴스를 생성해서 반환한다.
=> 핵심은 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다. 여기서 @PostContruct
같은 메서드는 호출 되지만 @PreDestory
같은 종료 메서드가 호출되지 않는다.
@PreDestory
같은 종료 메서드가 실행되지 않는다싱글톤 빈과 프로토타입 빈을 같이 쓸 때 주의점!
스프링 컨테이너에 요청
applicationContext
를 가지고 프로토타입 빈을 직접 로직에서 가지고 와서 사용한다.ObjectProvider
이다. 참고로 과거에는 ObjectFactory
가 있었는데 여기에 편의 기능을 추가해서 ObjectProvider
가 만들어졌다@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
-> 주입 받아 사용
javax.inject:javax.inject:1
라이브러리를 gradle
에 추가
참고: 실무에서 자바 표준인 JSR-330 Provider를 사용할 것인지, 아니면 스프링이 제공하는 ObjectProvider를 사용할 것인지??
ObjectProvider
는 DL을 위한 편의 기능을 많이 제공해주고 스프링 외에 별도의 의존관계 추가가 필요 없기 때문에 편리하다. 만약(정말 그럴일은 거의 없겠지만) 코드를 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야 한다면JSR-330 Provider
를 사용해야한다.스프링을 사용하다 보면 이 기능 뿐만 아니라 다른 기능들도 자바 표준과 스프링이 제공하는 기능이 겹칠때가 많이 있다. 대부분 스프링이 더 다양하고 편리한 기능을 제공해주기 때문에, 특별히 다른 컨테이너를 사용할 일이 없다면, 스프링이 제공하는 기능을 사용하면 된다.
Request, Session, Application, Websocket 등이 있다.
웹 환경에만 동작한다!
→스프링에서 해당 스코프에 따라 생성되고 자동 소멸되게끔 해준다.
직접 request scope로 로거를 실행하고 싶다면, 로직 실행 시점에 ObjectProvider 등으로 매번 빈을 찾아서 실행해야한다.
하지만 프록시 모드를 사용한다면, 가짜 객체를 빈 생성 시점에 의존성 주입하고, 로직을 호출할 때 그 때 컨테이너에서 찾아서 가져와준다.
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
→ 가짜 프록시 객체를 이용하여 Bean을 주입해준다.
→ 가짜 프록시 빈은 내부에 실제 Bean의 찾는 방법을 가지고 있다.
→ 클라이언트가 빈의 메서드를 호출하게 되면 가짜 프록시 객체의 메서드를 호출한 것이다.
→ 가짜 프록시 객체는 request 스코프의 진짜 메서드를 호출한다.
→ 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다(다형성)
"동작 정리"