개발놀이터

스프링 빈 스코프 본문

Spring/Spring

스프링 빈 스코프

마늘냄새폴폴 2021. 8. 16. 01:29

*빈 스코프
일반적으로 스프링 빈은 스프링 컨테이너의 시작과 함께 생성되어서 스프링 컨테이너가 종료될 때 까지 유지된다. 이것은 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문이다. 스코프는 번역 그대로 빈이 존재할 수 있는 범위를 뜻한다.

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


2. 프로토타입
싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환한다. 반면에 프로토타입 스코프를 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다.
핵심은 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다는 것이다. 클라이언트에 빈을 반환하고, 이후 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않는다. 프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있다. 그래서 @PreDestroy같은 종료 메서드가 호출되지 않는다.

프로토타입을 사용할 때 싱글톤과 같이 사용하면 문제가 생긴다.
싱글톤 패턴의 기본 빈에서 프로토타입 빈을 주입받을 때 싱글톤 타입의 빈이 호출될 때마다 이미 주입받은 프로토타입의 빈을 호출하기 때문에 만약 두명의 클라이언트가 각각 싱글톤 패턴의 빈을 호출한다면 원래 의도는 두개의 프로토타입이 생성되어야 하지만 스프링은 이미 주입받은 프로토타입을 클라이언트에게 제공하기 때문에 같은 프로토타입의 빈이 클라이언트에게 제공된다. 이를 해결하기 위해 스프링의 기능중 ObjectProvider를 사용하거나 JSR-330 Provider를 사용하면 된다. 

2-1. ObjectProvider를 이용하는 방법

static class ClientBean {
        private final ObjectProvider<PrototypeBean> prototypeBeanProvider;

        public ClientBean(ObjectProvider<PrototypeBean> prototypeBeanProvider) {
            this.prototypeBeanProvider = prototypeBeanProvider;
        }

        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }


지정한 빈을 컨테이너에서 대신 찾아주는 DL(Dependency Lookup)서비스를 제공하는 것이 바로 ObjectProvider이다. ObjectProvider는 getObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다. 때문에 logic을 호출할 때마다 객체를 new 해서 만든것과 같은 효과를 볼 수 있다.

2-2. JSR-330 Provider를 이용하는 방법 build.gradle에 implementation 'javax.inject:javax.inject:1'을 추가해주면 이용 가능하다.

static class ClientBean {
        private final Provider<PrototypeBean> prototypeBeanProvider; //Provider는 javax.inject에 있는 Provider를 사용해야 한다. security에 있는 Provider는아니다.

        public ClientBean(Provider<PrototypeBean> prototypeBeanProvider) {
            this.prototypeBeanProvider = prototypeBeanProvider;
        }

        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.get();
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }


둘 중 무엇을 써야하나 고민이 될텐데 ObjectProvider는 스프링에서 제공하는 기능이고 JSR-330 Provider는 자바 표준기능이다. 둘다 장단점이 있는데 ObjectProvider는 스프링이 제공하는 기능이기 때문에 사용하기 편리하고 제공하는 편리기능이 많이 있다.
JSR-330 Provider는 자바 표준이기 때문에 스프링컨테이너가 아닌 다른 곳에서도 사용이 가능하고 DL의 기능인 get()메서드만 제공하기 때문에 편리기능이 많이 없다 그리고 라이브러리를 추가해야한다는 약간의(?) 귀찮음이 있다.

둘 중 그냥 끌리는것을 선택해서 사용하면 될 것이다.


3. 웹 스코프
웹 스코프의 특징으로는 웹 환경에서만 동작한다는 것과, 웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료시점까지 관리한다는 것이다.

웹 스코프의 종류

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

 

session, application, websocket은 request스코프와 비슷한 원리로 동작하기 때문에 request스코프만 본문에서 다루도록 한다. 나머지 세개와 request와의 차이점은 적용되는 범위이다.

request 스코프 
동시에 여러 HTTP요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기가 어렵다. 이럴때 사용하기 좋은것이 바로  request 스코프이다.

의존관계에 있는 클래스를 request 스코프로 지정하고 실행해보면 오류가 나는데 그 이유는 의존관계에 있는 request 스코프는 request 즉 요청이 왔을 때 부터 요청이 끝나는 시간까지 생명주기를 가지고 있는데 스프링을 동작하려고 하는 그 순간에는 요청이 안온 상태이니 스프링 입장에서는 요청이 안왔는데 요청온 것을 의존관계로 주입하려고 하니
야, request 없는데? 라는 오류를 내보내는 것이다. 그렇다면 request 스코프를 사용할 수 없는 것인가? 바로 이럴 땐 두가지 해결방법이 있다.
바로 ObjectProvider와 proxy를 사용하는 것이다.

3-1-1. ObjectProvider 사용
의존 관계를 주입하려는 클래스의 스코프가 request라면 ObjectProvider<MyLogger> objectProvider; 를 선언한 후에 생성자를 만들어주거나 롬복을 이용해서 RequiredArgsConstructor를 어노테이션으로 설정해주면 된다. 그 후에 MyLogger myLogger = myLoggerProvider.getObject(); 로 MyLogger에 접근할 수 있다. ObjectProvider를 사용하면 getObject()가  나타난 시점에 스프링 컨테이너를 통해 해당 빈을 찾아서 반환하기 때문에 선언되면 그때 만들어서 주입해준다. 

3-1-2. proxy 사용
스코프를 request로 지정한 클래스에서 @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)를 선언해 주고 private final MyLogger myLogger; 이런식으로 바로 사용해주면 된다. 저 프록시모드가 앞선 Provider의 역할을 대신 하고 있다고 보면 된다. MyLogger가 사용되는 시점 (ex) myLogger.setRequestURL(requestURL); )에 진짜 MyLogger를 찾아서 주입하고 그 전까지는 스프링이 만든 가짜를 집어넣어 놓는다. 이렇게 프록시를 사용하면 마치 싱글톤을 사용하듯이 사용할 수 있는데, 이때 프록시는 싱글톤과 다르게 동작하기 때문에 주의점이 필요하다

3-1-2-1. 프록시 사용시 주의점
1. 마치 싱글톤을 사용하는 것 같지만 다르게 동작하기 때문에 결국 주의해서 사용해야 한다.
2. 이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용해야한다. 무분별하게 사용하면 유지보수하기 어려워진다.

'Spring > Spring' 카테고리의 다른 글

스프링 로깅  (0) 2021.08.19
스프링 MVC 구조  (0) 2021.08.19
스프링 빈 생명주기 콜백  (0) 2021.08.16
스프링 @Autowired, @Component, @Qualifier, @Primary  (0) 2021.08.15
스프링빈 조회  (0) 2021.08.13