스프링 빈 생명주기 콜백과 빈 스코프

Xonic·2022년 5월 15일
0

Java-Spring

목록 보기
4/7
post-thumbnail

빈 생명주기 콜백

DB 커넥션 풀이나 네트워크 소켓처럼 애플리케이션 시작 시점에 연결을 미리 해두고, 종료시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다.

스프링 빈은 간단하게 다음과 같은 라이프 사이클을 가진다.
객체 생성 -> 의존 관계 주입

  • 스프링 빈은 객체를 생성하고, 의존 관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다.
  • 스프링은 의존관계 주입이 완료 되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공한다
  • 또한 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다.

스프링 빈의 이벤트 라이프사이클
스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸전 콜백 → 스프링 종료

"초기화 콜백" : 빈이 생성되고 빈의 의존관계 주입이 완료된 후 호출

"소멸전 콜백" : 빈이 소멸되기 직전에 호출

참고! : 객체의 생성과 초기화를 분리하자.
생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면에 초기화는 이렇게 생성된 값을 활용해서 외부 커넥션을 연결하는 등 무거운 동작을 수행한다.

따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다는 객체를 생성하는 부분과 초기화 하는 부분을 명확하게 나누는 것유지 보수 관점에서 좋다.
물론 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우에는 생성자에서 한번에 다 처리하는게 좋다.

스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.

  • 인터페이스(InitializingBean, DisposableBean)
  • 설정 정보에 초기화 메서드, 종료 메서드 지정
  • @PostConstruct, @PreDestroy 애노테이션 지원

InitializingBean, DisposableBean 인터페이스

  • Bean 에 implements InitializingBean, DisposableBean 구현한다.
  • InitializingBeanafterPropertiesSet()메서드로 초기화를 지원한다.
  • DisposableBeandestroy() 메서드로 소멸을 지원한다.

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 이름을 주면 해당 BeanMethod 이름으로 알아서 호출해준다.

설정 정보 사용 특징

  • 메서드 이름을 자유롭게 줄 수 있다.
  • 스프링 빈이 스프링 코드에 의존하지 않는다.
  • 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료
    메서드를 적용할 수 있다.

종료 메서드 추론

  • @BeandestroyMethod 속성에는 아주 특별한 기능이 있다.
  • 라이브러리는 대부분 close, shutdown 이라는 이름의 종료 메서드를 사용한다.
  • @BeandestroyMethod 는 기본값이 (inferred)(추론)으로 등록되어 있다.
  • 이 추론 기능은 close, shutdown 라는 이름의 메서드를 자동으로 호출해준다. 이름 그대로 종료 메서드를 추론해서 호출해준다.
  • 따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 잘 동작한다.
  • 추론 기능을 사용하기 싫으면 destroyMethod="" 처럼 빈 공백을 지정하면 된다.

어노테이션 @PostContruct, @PreDestory

@PostConstruct, @PreDestroy 애노테이션 특징

  • 최신 스프링에서 가장 권장하는 방법이다.
  • 애노테이션 하나만 붙이면 되므로 매우 편리하다.
  • 패키지를 잘 보면 javax.annotation.PostConstruct 이다. 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
  • 컴포넌트 스캔과 잘 어울린다.
  • 유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다. 외부 라이브러리를 초기화, 종료 해야하면 @Bean의 기능을 사용하자.

최근은 @PostConstruct,@PreDestroy을 가장 권장!!

  • 생성이 된 후에, 소멸되기 전에 호출할 method를 스프링 빈으로 등록한 클래스 메서드에 애노테이션을 적어주면 스프링이 해준다.

빈 스코프

Spring Container의 Scope

  • Singleton: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지하는 가장 넓은 범위의 스코프다.
  • Prototype: 스프링 컨테이너가 빈의 생성과 의존관계 주입까지만 관여하고 더는 관여하지 않는 스코프다.
  • 웹 관련 스코프
  1. request: 웹 요청이 들어오고 나갈 때까지 유지되는 스코프다.
  2. session: 웹 세션이 생성되고 종료될 때까지 유지되는 스코프다.
  3. application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프다.

스코프 지정 방법

  • 자동 컴포넌트 스캔시 지정 방법
@Scope("prototype")
@Component
public class Bean {}
  • 수동 컴포넌트 스캔시 지정 방법
@Scope("prototype")
@Bean
public Bean bean() {}

프로토 타입 스코프

싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스를 반환한다. 반면 프로토타입 스코프를 스프링 컨테이너에 조회하면 항상 새로운 인스턴스를 생성해서 반환한다.

프로토 타입 빈

처음 스프링 컨테이너에 요청시 이 시점에 프로토타입 빈을 생성하며 필요한 의존 관계를 주입하며, 생성한 프로토타입 빈을 클라이언트에게 반환한다.
이후 스프링 컨테이너에서 프로토 타입빈을 다시 조회하더라도 항상 새로운 인스턴스를 생성해서 반환한다.
=> 핵심은 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다. 여기서 @PostContruct 같은 메서드는 호출 되지만 @PreDestory 같은 종료 메서드가 호출되지 않는다.

  • 싱글톤 빈은 컨테이너 생성 시점에 초기화 메서드가 실행되지만, 프로토타입 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 메서드도 실행된다.
  • 싱글톤 빈은 스프링 컨테이너의 관리 대상이기 때문에, 스프링 컨테이너가 종료될 때 빈의 종료 메서드가 실행되지만, 프로토타입 빈은 스프링 컨테이너가 생성과 의존관계 주입 그리고 초기화까지만 관여하고, 더는 관여하지 않는다. 따라서 프로토타입 빈은 스프링 컨테이너가 종료될 때 @PreDestory 같은 종료 메서드가 실행되지 않는다

싱글톤 빈과 프로토타입 빈을 같이 쓸 때 주의점!

  • 싱글톤 빈의 생성 시점에 프로토타입의 빈이 주입되기 때문에 (싱글톤 빈에서의 생성자 주입을 통해) 스코프 범위가 맞지 않는 것을 주의해야한다.

프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결

  • 싱글톤 빈과 프로토타입 빈을 함께 사용할 때, 어떻게 하면 사용할 때 마다 항상 새로운 프로토타입 빈을 생성할 수 있을까?

스프링 컨테이너에 요청

  • 막가파로 코드짜면, 직접 applicationContext를 가지고 프로토타입 빈을 직접 로직에서 가지고 와서 사용한다.
  • 의존 관계를 외부에서 주입(DI) 받는게 아니라 이렇게 직접 필요한 의존관계를 찾는 것을 Dependency Lookup (DL) 의존 관계 조회(탐색) 이라 한다.
  • 스프링에는 지정한 프로토 타입 빈을 컨테이너에서 대신 찾아주는 딱 DL 정도의 기능만 제공하는 무언가가 있다!!

ObjectFactory, ObjectProvider

  • 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 바로 ObjectProvider이다. 참고로 과거에는 ObjectFactory가 있었는데 여기에 편의 기능을 추가해서 ObjectProvider가 만들어졌다
@Autowired
  private ObjectProvider<PrototypeBean> prototypeBeanProvider;

-> 주입 받아 사용

JSR-330 Provider

javax.inject:javax.inject:1 라이브러리를 gradle에 추가

참고: 실무에서 자바 표준인 JSR-330 Provider를 사용할 것인지, 아니면 스프링이 제공하는 ObjectProvider를 사용할 것인지??
ObjectProvider는 DL을 위한 편의 기능을 많이 제공해주고 스프링 외에 별도의 의존관계 추가가 필요 없기 때문에 편리하다. 만약(정말 그럴일은 거의 없겠지만) 코드를 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야 한다면 JSR-330 Provider를 사용해야한다.

스프링을 사용하다 보면 이 기능 뿐만 아니라 다른 기능들도 자바 표준과 스프링이 제공하는 기능이 겹칠때가 많이 있다. 대부분 스프링이 더 다양하고 편리한 기능을 제공해주기 때문에, 특별히 다른 컨테이너를 사용할 일이 없다면, 스프링이 제공하는 기능을 사용하면 된다.

Web Scope

Request, Session, Application, Websocket 등이 있다.

웹 환경에만 동작한다!

  • request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.
  • session: HTTP Session과 동일한 생명주기를 가지는 스코프
  • application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
  • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

→스프링에서 해당 스코프에 따라 생성되고 자동 소멸되게끔 해준다.

직접 request scope로 로거를 실행하고 싶다면, 로직 실행 시점에 ObjectProvider 등으로 매번 빈을 찾아서 실행해야한다.

하지만 프록시 모드를 사용한다면, 가짜 객체를 빈 생성 시점에 의존성 주입하고, 로직을 호출할 때 그 때 컨테이너에서 찾아서 가져와준다.

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)

→ 가짜 프록시 객체를 이용하여 Bean을 주입해준다.

→ 가짜 프록시 빈은 내부에 실제 Bean의 찾는 방법을 가지고 있다.

→ 클라이언트가 빈의 메서드를 호출하게 되면 가짜 프록시 객체의 메서드를 호출한 것이다.

→ 가짜 프록시 객체는 request 스코프의 진짜 메서드를 호출한다.

→ 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다(다형성)

"동작 정리"

  • CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.
  • 이 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있다.
  • 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다.

참조

인프런 김영한님 강의

profile
공부 한 것을 공유하는 블로그입니다.

0개의 댓글