상세 컨텐츠

본문 제목

[스프링1] 빈 생명주기 콜백, 빈 스코프

22-23/22-23 Spring 1

by YUZ 유즈 2022. 10. 21. 10:00

본문

728x90

❗ 스프링 빈 콜백

📌 스프링 빈 라이프사이클

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

 

스프링 컨테이너는 스프링 빈을 생성하는 동시에 해당 빈에 존재하는 모든 의존관계를 주입한다. 이 과정이 종료되면 초기화 작업이 시작되는데, 스프링에서는 초기화 시점에 호출할 수 있는 콜백 메서드를 제공한다. 또한, 비슷한 맥락에서 스프링 빈이 소멸되기 직전에 호출 가능한 콜백 메서드도 제공한다.

 

❗ 스프링이 제공하는 빈 생명주기 콜백 방식

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

📌 인터페이스 InitializingBean, DisposableBean

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class NetworkClient implements InitializingBean, DisposableBean {
		...
    	@Override
    	public void afterPropertiesSet() throws Exception {
			// 초기화 시점에 실행되는 메서드
		}
		
    	@Override
    	public void destroy() throws Exception {
        	// 소멸 시점에 실행되는 메서드
    	}
}

InitializingBean, DisposableBean은 스프링에서 제공하는 인터페이스로, 실제 사용하고자 하는 클래스에 implements하여 사용하면 된다.

 

인터페이스 단점

  • 이 인터페이스는 스프링 전용 인터페이스다. 해당 코드가 스프링 전용 인터페이스에 의존한다.
  • 초기화, 소멸 메서드의 이름을 변경할 수 없다.
  • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.

다음과 같은 단점 때문에 현재는 잘 사용하지 않는 방법이다. 

 

📌 빈 등록 시 초기화 메서드, 소멸 메서드 지정

빈 등록 시 설정 정보에 메서드 이름을 지정하는 방법이다. 다음과 같은 문법으로 사용할 수 있다.

 

- 초기화, 소멸 메서드 작성

public void init() throws Exception {
	// 초기화 시점에 실행되는 메서드 작성
}

public void close() throws Exception {
	// 소멸 직전에 실행되는 메서드 작성
}

- 빈 등록 시 해당 메서드 지정

@Configuration
static class LifeCycleConfig {
	@Bean(initMethod = "init", destroyMethod = "close")
    public NetworkClient networkClient() {
    	// 생성자
    }
}

설정 정보 단점

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

종료메서드(destroyMethod) 추론 기능

  • @Bean의 destroyMethod 속성에 존재하는 기능
  • @Bean의 destroyMethod 는 기본값이 (inferred) (추론)으로 등록되어 있다.
  • 이 추론 기능은 close , shutdown 라는 이름의 메서드를 자동으로 호출해준다.
    • 라이브러리에서 보편적으로 사용하는 이름이라 자동 호출하도록 설정됨

 

📌 애노테이션 @PostConstruct, @PreDestroy

스프링에서 공식적으로 권장하는 방법이다. 이렇게 등록만 해주면 다른 코드는 고칠 필요 없이 적용된다.

@PostConstruct
public void init() throws Exception {
	// 초기화 시점에 실행되는 메서드
}

@PreDestroy
public void close() throws Exception {
	// 소멸 직전에 실행되는 메서드
}

스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.

 

❗ 빈 스코프

📌 빈 스코프 종류

  • 싱글톤 : 스프링 컨테이너와 함께 생성되고 종료되는 스코프. 가장 넓은 범위를 가짐
  • 프로토타입 : 스프링 컨테이너가 빈 생성과 의존관계 주입까지만 수행하고 더 이상 관리하지 않는 스코프
  • 웹 관련 스코프
    • request : 웹 요청이 들어오고 나갈 때까지 유지되는 스코프
    • session : 웹 세션 생성과 종료까지 유지되는 스코프
    • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

❗ 프로토타입 스코프

프로토타입 빈을 스프링 컨테이너에 요청하면 컨테이너는 그때마다 새로운 프로토타입 빈 객체를 반환한다.

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

📌 싱글톤 빈 안에서 프로토타입 빈을 호출했을 때 발생하는 문제

싱글톤 빈은 생성 시점에만 의존관계를 주입받는다. 이때 의존관계에 있는 객체 중 프로토타입 스코프를 가진 빈이 있다면, 생성 시점에 하나의 빈을 받고 이후에도 계속 같은 빈을 사용하게 된다. 프로토타입 빈임에도 싱글톤 빈처럼 사용되는 것이다.

 

📌 문제 해결 방법 - Provider

@Autowired
private ApplicationContext ac;
public int logic() {
	PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
    prototypeBean.addCount();
    int count = prototypeBean.getCount();
    return count;
}

의존관계를 외부에서 주입(DI) 받는게 아니라 이렇게 직접 필요한 의존관계를 찾는 것을 Dependency Lookup(DL) 의존관계 조회(탐색) 이라 한다. ⇒ 스프링 컨테이너에 종속적인 코드가 된다.

 

📌 문제 해결 방법 - ObjectFactory, ObjectProvider

@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
	PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
    prototypeBean.addCount();
    int count = prototypeBean.getCount();
    return count;
}

prototypeBeanProvider.getObject()을 통해 항상 새로운 프로토타입 빈이 생성된다.

 

ObjectProvider 의 getObject() 를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다(DL).

 프로토타입 빈이므로 요청할 때마다 새로운 객체를 만들어서 반환

 

ObjectFactory, ObjectProvider 특징

  • ObjectFactory: 기능이 단순, 별도의 라이브러리 필요 없음, 스프링에 의존
  • ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리 등 편의 기능이 많고, 별도의 라이브러리 필요 없음, 스프링에 의존

📌 문제 해결 방법 - JSR-330 Provider

@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
	PrototypeBean prototypeBean = provider.get();
    prototypeBean.addCount();
    int count = prototypeBean.getCount();
    return count;
}
  • 매우 단순한 것이 장점이다. 딱 get 함수만 존재한다.
  • 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용이 가능하다.
  • 순환 참조 문제를 간단하게 해결 가능하다.

❗ 웹 스코프

📌 웹 스코프 종류

  • request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프
  • application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
  • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

📌 request 스코프 사용 방법

1. 웹 라이브러리 추가(build.gradle)

implementation 'org.springframework.boot:spring-boot-starter-web'

2. 클래스 작성

@Component
@Scope(value = "request") // request 스코프 지정
public class MyLogger {
	// 코드 작성
}

3. 컨트롤러 작성

@RequestMapping("log-demo") // 요청 url
@ResponseBody
public String logDemo(HttpServletRequest request) { // 초기화 당시 request가 없음
	String requestURL = request.getRequestURL().toString();
    myLogger.setRequestURL(requestURL);
    return "OK";
}

이런 식으로 작성하면, 초기화 작업 당시 request가 비어있어 오류가 난다.

request는 하나의 HTTP 요청에 매핑되기 때문에 요청이 들어오기 전까지는 null 값이다.

 

📌 문제 해결법 - 프록시 적용

proxyMode

  • TARGET_CLASS : 적용 대상이 클래스
  • INTERFACES : 적용 대상이 인터페이스
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
	// 코드
}

proxyMode 애노테이션을 설정해주면, 아래와 같은 일들이 일어난다.

정리

  • CGLIB라는 라이브러리로 내 클래스를 상속받은 가짜 프록시 객체를 만들어서 주입한다.
  • 이 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있다.
  • 가짜 프록시 객체는 실제 request scope와는 관계가 없다. 그냥 가짜이고, 내부에 단순한 위임 로직만 있고, 싱글톤처럼 동작한다.

 

Quiz.

1. 스프링 빈의 이벤트 라이프사이클을 나열하시오.

2. 스프링이 제공하는 빈 생명주기 콜백 3가지를 나열하시오.

3. 종료메서드(destroyMethod)에는 기본적으로 (           ) 기능이 존재한다.

4. 스프링 컨테이너가 빈 생성과 의존관계 주입까지만 수행하고 더 이상 관리하지 않는 스코프는?

5. 의존관계 주입 대신 의존관계 조회를 통해 주입을 지연시키는 방법에는 어떤 것이 있는가?


Spring 1

EDITOR: PORO

728x90

관련글 더보기