프로필사진

Go, Vantage point

가까운 곳을 걷지 않고 서는 먼 곳을 갈 수 없다.


Github | https://github.com/overnew/

Blog | https://everenew.tistory.com/





티스토리 뷰

반응형

 

 

 

* 김영한님의 스프링 핵심원리 기본편 강좌를 수강하며 정리한 글입니다. *

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

 

 

이전 글에서는 빈의 라이프 사이클에 대해 다루었다.

 

 

스코프란 프로그래밍 언어에서 변수의 생존 유효 범위이다.

스프링 빈에는 여러 가지 스코프가 있다.

 

싱글톤 스코프

컨테이너의 시작에서 종료까지 유지되는 가장 넓은 범위의 스코프로, default로 적용되는 스코프.

하나의 인스턴스를 끝까지 유지해주는 싱글톤 패턴이 적용되는 스코프이다.

 

@Test
void singletonBeanFind(){
	//컨테이너 생성 시점에 빈 등록
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);

	//컨테이너 종료 시점에 제거됨
    ac.close();
}

 

프로토타입 스코프

빈의 생성과 의존 관계 주입을 진행 후, 클라이언트에게 반환하고 컨테이너가 더 이상 관리하지 않는 스코프이다.

즉, 조회 시마다 항상 새로운 인스턴스를 생성(빈 생성, DI, 초기화까지만 진행)해 반환한다.

만약 소멸 메서드를 호출해야 한다면 관리 책임은 클라이언트에게 있다. 

 

 

 

 

 

 

테스트를 해보면 컨테이너 생성 시점이 아닌 getBean()으로 조회 시, 생성과 초기화가 진행된다.

또한 조회마다 다른 인스턴스를 반환하고, 컨테이너를 close() 해도 빈의 destory()가 호출되지 않음을 확인할 수 있다.

 

 

그런데 만약 싱글톤 스코프와 프로토타입 스코프를 같이 사용하면 어떻게 될까?

싱글톤 빈의 내부에 프로토타입 빈을 주입받아 사용하는 경우를 생각해보자.

 

싱글톤 빈이 생성 시점에 의존관계 주입을 위해 프로토타입 빈이 생성되어 주입된다.

이 프로토타입 빈 내부 값으로서, 싱글톤 빈이 소멸될 때까지 유지하기 때문에 모든 클라이언트는 같은 프로토타입 빈에 접근하게 된다.

 

@Scope("singleton")
static class SingletonBean{
    //생성 시점에 주입된 후 불변
    private final PrototypeBean prototypeBean;

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

 

하지만 분명, 프로토타입 스코프를 적용한 목적은 호출마다 새로운 빈을 생성받기 위함일 것이다.

이를 해결하는 방법들은 다음과 같다.

 

 

1. Dependency Lookup(DL) 의존관계 조회

@Autowired
private ApplicationContext ac;

ApplicationContext를 주입받고 프로토타입 빈이 필요할 때마다 ac.getBean("prototypeBean")을 조회해 생성시키는 방식이다.

하지만 스프링 컨테이너에 종속적인 코드가 된다.

 

 

2. ObjectProvider<>, ObjectFactory<>

 

ObjectProvider는 ObjectFactory를 상속한 클래스로 추가적인 기능을 몇 가지 더 제공한다.

두 클래스 모두 ObjectProvider.getObject()를 호출한 시점에 컨테이너에서 빈을 꺼내 준다.

해당 타입이 프로토타입 빈이라면 해당 시점에 새로운 인스턴스가 생성된다.

 

 

 

컨테이너의 Dependency Lookup을 대신 진행해주는 대리자 역할을 한다.

단, 스프링 프레임워크에서 제공하므로 스프링 의존적 코드가 된다.

 

 

3. JSR-330 자바 표준 Provider

 

gradle의 dependencies에 아래의 라이브러리를 추가하자.

implementation 'javax.inject:javax.inject:1'

 

 

 

Provider는 .get()으로 정확히 같은 역할을 수행한다.

자바 표준이므로 스프링 이외의 컨테이너에도 사용할 수 있다.

 

이러한 DL은 대부분 lazy한 주입이 필요할 때 사용한다.

 

 

사실 스프링 내에서는 자바 표준보단 더 다양한 스프링 기능을 사용할 때가 많다.

 

 

웹 스코프

웹 환경에서만 동작하고, 컨테이너가 소멸 시점(소멸 메서드 호출)까지 관리해 준다.

 

request: 각각의 HTTP 요청이 들어오고 나갈 때까지 유지

session: HTTP 세션이 생성, 종료까지 유지

application: 서블릿 컨텍스트와 같은 범위로 유지

 

각 클라이언트의 요청에 대해 할당되는 request 스코프 빈은 해당 클라이언트가 사용하는 기능에 대해서는 같은 전용 빈은 사용한다. 해당 클라이언트로 HTTP 응답이 나가면 그 시점에 전용 빈이 소멸된다.

 

이때 서로의 빈을 구분하는 것이 전 세계에서 유일한 ID를 생성해 주는 UUID이다.

이것은 생성 시점에 초기화 콜백으로 초기화시켜주자.

@PostConstruct  //고객 요청시
public void init(){
    //전세계에서 유니크한 ID 생성됨
    String uuid = UUID.randomUUID().toString();
}

 

gradle에 implementation 'org.springframework.boot:spring-boot-starter-web'를 추가하면

스프링 부트의 내장 톰켓 서버로 웹 서버를 실행시킬 수 있다.

 

request scope 예제를 만들 때 필요한 것이 클라이언트의 요청인데, 이를 해결하는 방법이 바로 Lazy하게 주입을 받는 Provider이다.

 

 

 

 

 

@Scope(value = "request")가 적용된 MyLogger는 클라이언트의 요청이 들어올 때 생성된다.

이를 사용하는 빈에 MyLogger를 생성시점에 DI 하려면 오류가 발생한다. (아직 요청이 들어와 생성된 게 아니기 때문에)

따라서 ObjectProvider로 DL이 가능한 클래스를 주입시킨다.

private final ObjectProvider<MyLogger> myLoggerProvider;

이후 사용 시 provider.getObject()를 통해 요청을 생성하면 된다.

동시에 여러 요청이 오더라도 클라이언트마다 각각의 request scope 빈을 만들어 준다.

 

 

테스트를 통해 확인해보면 요청 시 클라이언트마다 전용 MyLogger 빈이 생성되어 서비스를 진행해주고 요청 반환 시 파기되는 것을 확인할 수 있다.

 

스코프의 프록시 모드 옵션

Scope의 옵션으로 proxyMode라는 것이 있다.

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

 

proxyMode가 적용되면 컨테이너가 CGLIB 라이브러리를 사용해 해당 클래스를 상속한 가짜 프록시 객체를 생성한다.

DI시에 이 프록시 객체가 주입되어, 조회하더라도 프록시 객체가 찾아진다.

 

프록시의 메서드가 호출되면 그때 진짜 빈을 조회하여 메서드를 대신 실행하게 된다. (Lazy하게 동작)

클라이언트 입장에서는 진짜 빈과 동일하게 진행되므로 신경을 쓸 필요가 없게된다.

(실제로 프록시 서버와 비슷한 역할을 해준다.)

 

 

이 가짜 프록시 객체를 이용하면 Provider를 이용하지 않고도 싱글톤 처럼 동작시킬 수 있다.

이 프록시 객체는 공유되기 때문에 request scope 빈을 싱글톤 빈 처럼 사용할 수 있게 만들어 준다.

단, 싱글톤처럼 사용하지만 내부 동작은 다르기 때문에 주의하자.

 

반응형
댓글
반응형
인기글
Total
Today
Yesterday
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함