쌓고 쌓다

빈 스코프 (singleton, prototype, request) 본문

프로그래밍/spring

빈 스코프 (singleton, prototype, request)

승민아 2023. 7. 17. 18:52

빈 스코프는 빈이 존재할 수 있는 범위이다.

스프링 빈은 기본적으로 싱글톤 스코프로 생성되기에 이때까지 스프링 컨테이너가 종료될 때까지 유지된다.

 

스코프 종류

  • 싱글톤 : 기본 스코프로 스프링 컨테이너 시작과 끝까지 유지된다.
  • 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는다.
  • 웹 관련 스코프
    • request : 웹 요청이 들어오고 나갈 때까지 유지되는 스코프
    • session : 웹 세션이 생성되고 종료될때까지 유지되는 스코프
    • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

 

 

빈 스코프 정의 방법

@Scope("스코프") 형식으로 정의한다.

 

1. 컴포넌트 스캔 자동 등록

@Scope("prototype")
@Component
public class Bean {}

 

2. 수동 등록

@Scope("prototype")
@Bean
PrototypeBean Bean() {
    return new PrototypeBean();
}

 

싱글톤 스코프

싱글톤 스코프 빈을 요청하면 스프링 컨테이너는 항상 같은 인스턴스를 반환한다.

 

프로토타입 스코프

프로토타입 스코프의 빈을 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 반환해 준다.

출처: 인프런 - 김영한

프로토타입 빈 조회 시

  1. 프로토타입 스코프의 빈 요청
  2. 스프링 컨테이너는 빈 생성과 의존관계 주입
  3. 스프링 컨테이너가 생성한 프로토타입 빈을 반환
  4. 이후에 스프링 컨테이너에 요청이 오면 항상 새로운 프로토타입 빈을 생성하여 반환

프로토타입 빈 특징

스프링 컨테이너는 프로토타입 빈 생성과 의존관계 주입, 초기화까지만 처리한다.

프로토타입 빈의 관리 책임은 빈을 받은 클라이언트에 있다. @PreDestory 같은 종료 메서드는 호출되지 않는다.

 

싱글톤 빈은 컨테이너 생성 시점에 초기화 생성 메서드가 실행된다.

프로토타입 스코프 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 메서드도 실행된다.

 

싱글톤 빈에서 프로토타입 빈 사용 문제

singletonBean이라는 싱글톤 빈이 프로토타입 빈을 주입받아 사용하는 경우를 생각해 보자.

 

SingletonBean, PrototypeBean 클래스

@Scope("singleton")
static class SingletonBean {
    private PrototypeBean prototypeBean;

    @Autowired
    public SingletonBean(PrototypeBean prototypeBean) {
    	this.prototypeBean = prototypeBean;
    }
    
    public PrototypeBean getPrototypeBean() {
        return prototypeBean;
    }
}

@Scope("prototype")
static class PrototypeBean {
    ...
}

 

SingletonBean을 조회할 때마다 PrototypeBean이 계속 새로 생성될까?

    @Test
    void singletonUsePrototype() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class, PrototypeBean.class);
        
        SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
        PrototypeBean prototypeBean1 = singletonBean1.getPrototypeBean();

        SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
        PrototypeBean prototypeBean2 = singletonBean2.getPrototypeBean();
        
        Assertions.assertThat(prototypeBean1).isSameAs(prototypeBean2);
        ac.close();
    }

=> SingletonBean을 2번 조회하여 각각 PrototypeBean 인스턴스를 조회하면 같은 PrototypeBean이 나온다.

 

싱글톤 빈은 스프링 컨테이너 생성 시점에 함께 생성되고 의존관계도 주입받기에 프로토타입 빈을 내부 필드에 보관한다.

SingletonBean 내부에 가지고 있는 PrototypeBean은 과거에 주입이 끝난 빈이다.

주입 시점에 스프링 컨테이너에 요청하여 프로토타입 빈이 새로 생성된 거지 사용할 때마다 새로 생성되는 게 아니다.

또한 여러 빈에서 프로토타입 빈을 주입받는다면 각기 다른 인스턴스로 주입받는다.

 

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class, PrototypeBean.class);

ANnotationConfigApplicationContext 생성 시 파라미터는 설정 정보 클래스뿐만 아니라 등록할 클래스를 넣어줄 수 있다.

 

Provider로 문제 해결

싱글톤 빈과 함께 사용할 때, 사용할 때마다 새로운 프로토타입 빈을 생성하게 하자.

 

1. 스프링 컨테이너에 요청

@Scope("singleton")
static class SingletonBean {

    @Autowired
    private ApplicationContext ac;

    public void logic() {
        PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
    	System.out.println("prototypeBean = " + prototypeBean);
    }

}

=> 위의 코드처럼 의존관계를 외부에서 주입받는 게 아니라

필요한 의존관계를 찾는 것을 DependencyLookup(DL) 의존관계 조회(탐색)이라고 한다.

 

지정한 빈을 컨테이너에서 찾아주는 DL 서비스를 ObjectProvider가 제공한다.

2. ObjectProvider

@Scope("singleton")
static class SingletonBean {

	@Autowired
	private ObjectProvider<PrototypeBean> prototypeBeanProvider;

	public void logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            System.out.println("prototypeBean = " + prototypeBean);
	}

}

prototypeBeanProvider.getObject()를 통해 항상 새로운 프로토타입 빈을 생성할 수 있다.

getObject()를 호출하면 해당 빈을 찾아 반환해 준다. (DL)

 

3. JSR-330 Provider

@Autowired
private Provider<PrototypeBean> provider;
    
public void logic() {
    PrototypeBean prototypeBean = provider.get();
}

자바 표준 방식이라 다른 컨테이너에서도 사용할 수 있다.

 

웹  스코프

  • 웹 환경에서만 웹 스코프가 동작한다.
  • 스프링이 해당 스코프의 종료 시점까지 관리해 준다. 종료 메서드(destroy) 또한 실행된다.

웹 스코프 사용 시 아래의 의존성 추가

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

 

 

종류

  • request : HTTP 요청 하나가 들어오고 나갈 때까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 인스턴스가 생성
  • session, application, websocket

 

HTTP request 요청 당 각각 할당되는 request 스코프

출처: 인프런 - 김영한

request 스코프는 HTTP 요청 하나에 생성되고 종료 시 소멸한다.

싱글톤 빈은 스프링 애플리케이션 실행 시점에 생성하여 주입이 가능하지만

request 스코프 빈은 요청이 들어와야지 사용할 수 있다. 그냥 클래스에서 주입하여 사용하면 에러가 난다.

 

스코프 빈 생성

1. Provider

MyLogger는 request 스코프이다.

private final ObjectProvider<MyLogger> myLoggerProvider;

myLoggerProvider.getObject();를 호출하는 시점까지 request scope 빈의 생성을 지연하자.

실제 HTTP 요청이 들어왔을 때 HTTP 요청이 진행 중이므로 myLoggerProvider.getObject(); 로 빈의 생성할 수 있다.

Controller, Service 단에서 각각 따로 myLoggerProvider.getObject();를 호출해도 같은 MyLogger 스프링 빈이 반환된다.

(같은 HTTP 요청이면 같은 빈이 반환된다는 것이다.)

 

2. 프록시

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
    ...
}

 

MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request 요청의 여부와 상관없이

가짜 프록시 클래스를 다른 빈에 미리 주입할 수 있다.

 

이제 생성자 주입을 통해 바로 넣어 사용할 수 있다. Like 싱글톤!

@Controller
@RequiredArgsConstructor
public class LogDemoController {
	private final LogDemoService logDemoService;
	private final MyLogger myLogger; 
}

 

웹 스코프와 프록시 작동 원리

System.out.println("myLogger = " + myLogger.getClass());
// myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$b68b726d

내 클래스를 상속받은 가짜 프록시 객체를 만들어서 주입해 준다.

 

스프링 컨테이너에서 myLogger 빈을 검색해 보면?

ac.getBean("myLogger", MyLogger.class)

 마찬가지이다. 프록시 객체가 스프링 컨테이너에 등록되어 있다.

즉, 의존관계 주입에도 가짜 프록시 객체가 등록되는 것이다.

 

그러면 언제 진짜 빈을 가져오는 건데?

출처: 인프런 - 김영한

가짜 프록시 객체에 요청이 오면 그때 진짜 빈을 요청하는 위임 로직이 돌아간다.

가짜 프록시 객체의 메서드(logic)을 실행하면 진짜 빈의 메서드를 실행한다.

Comments