본문 바로가기

Bean scopes

컨테이너로부터 bean 을 호출하였을 때, 요청 수 만큼의 bean 인스턴스를 반환할 것인지, 하나의 bean 인스턴스로 계속해서 반환할 것인지 등을 <bean /> 요소의 scope 속성을 설정하거나, 컴포넌트에 @RequestScope 처럼 @*Scope 어노테이션을 사용하여 제어할 수 있다. Spring Framework 는 7가지의 scope 를 지원하며 사용자 정의 scope 를 만들 수도 있다.



singleton scope


scope 를 지정하지 않은 bean 은 singleton scope 이다(default). Spring IoC 컨테이너는 해당 bean 정의의 객체 인스턴스를 정확히 하나 생성한다. 이 인스턴스는 singleton bean 의 캐시에 저장되고, 그 이후의 모든 요청과 해당 이름의 Bean 에 대한 참조는 캐시된 객체를 반환한다.


prototype scope


prototype scope 로 설정된 bean 은 요청이 있을 때마다 새로운 bean 인스턴스를 생성한다. 컨테이너는 prototype 객체를 인스턴스화하여 클라이언트에 전달한 이후에는 해당 객체의 라이프 사이클을 관리하지 않기 때문에, 클라이언트에서 bean post-processor 등을 이용하여 리소스를 해제해야 한다.


singleton bean 이 prototype bean 을 의존성 주입하면 prototype bean 이 인스턴스화 되고 singleton bean 으로 주입되지만, 런타임시 반복적으로 prototype bean 의 새로운 인스턴스를 요청한다면 메소드 주입을 사용해야 한다. 원칙적으로, 모든 stateful bean 에 대해 prototype scope 을 사용하고, stateless beans 에 대해 singleton scope 를 사용해야 한다.



다음 5가지 scope 는 웹 기반 Spring ApplicationContext 를 구현(예: XmlWebApplicationContext) 하는 경우에만 사용할 수 있다. 웹 기반 구성시에 Servelet 2.5 는 ServletRequestListener 를 구현한 org.springframework.web.context.request.RequestContextListener 를 web.xml 파일에 등록해야 하며, Servlet 3.0 이상은 WebApplicationInitializer 인터페이스를 통해 프로그래밍 방식으로 대체할 수 있다. Spring Web MVC 의 Spring DispatcherServlet 이나 DispatcherPortlet 에 의해 처리되는 요청에 대해서는 특별한 설정이 필요없다. 만약 리스너 설정에 문제가 있는 경우 Spring 의 RequestContextFilter 를 사용할 수 있으며, DispatcherServlet, RequestContextListener, RequestContextFilter 는 모두 요청을 처리하는 Thread 에 HTTP 요청 객체를 바인딩하는 동일한 작업을 수행하므로, request 와 session scope bean 을 사용할 수 있다.



Request scope


컨테이너는 request scope 로 설정된 bean 에 HTTP 요청이 들어오면, 각 요청마다 새로운 인스턴스를 생성한다. 인스턴스들은 각 요청 별로 각각 동작하며, 요청 처리가 완료되면 해당 bean 은 버려진다.


Session scope


컨테이너는 단일 HTTP Session 의 라이프 사이클 동안 session bean 의 새로운 인스턴스를 생성한다. HTTP Session 이 폐기되면, 해당 bean 도 삭제된다.


Global session scope


HTTP Session scope 와 비슷하며, portlet 기반의 웹 어플리케이션 컨텍스트에서만 적용된다. portlet 기반의 웹 어플리케이션을 구성하는 모든 portlet 간에 해당 bean 은 공유된다. 표준 Servlet 기반의 웹 어플리케이션에서 사용한다면 오류는 발생하지 않지만, HTTP Session scope 로 사용될 것이다.


Application scope


컨테이너는 application scope 로 설정된 bean 정의를 한 번 사용하여 새로운 인스턴스를 생성한다. singleton scope 과 비슷하지만 singleton bean 는 ApplicationContext 당 하나의 인스턴스로 생성되고, application bean 은 ServletContext 당 하나의 인스턴스로 생성되며 일반적인 ServletContext 속성으로 저장된다.


Websocket scope


websocket scope 는 일반적으로 singleton 이다. websocket bean 은 "clientInboundChannel" 에 등록된 컨트롤러 및 모든 채널 인터셉터에 주입할 수 있으며, 이들은 개별 websocket 세션보다 오래 유지된다. 따라서 websocket bean 은 scope 프록시 모드를 사용해야 한다.




프록시 주입


HTTP request, session, globalSession bean 을 다른 bean 으로 주입하려면, scope 가 지정된 bean 에 AOP 프록시를 주입해야 한다. 그렇지 않으면 singleton bean 이 컨테이너에서 한 번 인스턴스화 되므로, 그에 주입될 scope bean 도 한 번만 주입될 것이다. 프록시를 생성하는 방법은 scope 가 지정된 bean 안에 <aop:scoped-proxy/> 요소를 삽입하면 된다.


<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>
 
<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
cs


위 예를 보면, singleton bean 인 userManager 는 session bean 인 userPreferences 를 의존한다. 이처럼 수명이 긴 userManager bean 이 짧은 userPreferences bean 을 주입할 때 프록시를 사용하면, 컨테이너는 userPreferences 와 완전히 동일한 객체와 public interface 를 만들어 UserPreferences 객체를 가져올 수 있다. 이 때 생성되는 프록시는 CGLIB 기반의 클래스 프록시이며 public 메소드 호출만 인식하는데, <aop:scoped-proxy/> 요소의 proxy-target-class 속성 값에 false 를 지정하여, CGLIB 이 아닌 표준 JDK 인터페이스 기반 프록시를 사용하면 public 이 아닌 다른 메소드의 호출도 인식할 수 있다.


singleton bean 간에 <aop:scoped-proxy/> 를 사용하면, 비직렬화시 중간 프록시를 거쳐 대상 singleton bean 을 다시 얻을 수 있다. prototype bean 에 <aop:scoped-proxy/> 를 선언하면, 공유된 프록시상의 모든 메소드 호출이 새로운 대상 인스턴스를 생성하게 된다. <aop:scoped-proxy/> 말고도 ObjectFactory<MyTargetBean> 로 주입 지점을 정의하면, 인스턴스를 유지하거나 별도로 저장하지 않고도 getObject() 호출로 필요할 때마다 현재 인스턴스를 검색할 수 있다. JSR-330 에서는 Provider<MyTargetBean> 을 정의하고 get() 호출로 사용 가능하다.



사용자 정의 scope


bean scope 를 사용자 정의하여 확장하는 것이 가능하다. 내장된 기존 scope 를 재정의 하는 것은 좋은 방법이 아니며 singleton 과 prototype 범위는 재정의 할 수 없다. 사용자 scope 를 만들어 Spring 컨테이너에 적용시키려면 org.springframework.beans.factory.config.Scope 인터페이스를 구현해야 한다. Scope 인터페이스는 scope 에서 객체를 가져오고, 객체를 제거하는데 사용하는 네 가지 메소드가 있다.


  • Object get(String name, ObjectFactory objectFactory)
    지정된 scope 에서 객체를 반환한다. 존재하지 않으면 메소드는 나중에 참조할 수 있도록 세션에 바인드 한 후 bean 의 새 인스턴스를 반환한다.

  • Object remove(String name)
    지정된 scope 에서 객체를 제거한다. 객체가 반환되지만, 없는 경우 null 을 반환한다.

  • void registerDestructionCallback(String name, Runnable destructionCallback)
    scope 나 scope 내의 객체가 소멸될 때 실행해야 하는 콜백을 등록한다.

  • String getConversationId()
    지정된 scope 에 대한 conversation 식별자를 가져온다.



하나 이상의 사용자 정의 scope 구현을 작성하고 테스트 한 후에는 Spring 컨테이너에 등록해야 사용할 수 있다. 아래 예제는 ConfigurableBeanFactory 인터페이스의 registerScope 메소드로 scope 를 등록하고 bean 을 정의한다.


Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
cs


<bean id="bar" class="x.y.Bar" scope="thread" />
cs


XML 메타데이터에서도 CustomScopeConfigurer 클래스를 사용하여 사용자 정의 scope 를 등록할 수 있다.


<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="thread">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>
cs


FactoryBean 구현에 <aop:scoped-proxy/> 를 배치하면, getObject() 에서 반환된 객체가 아닌 scope 가 지정된 factory bean 자체가 된다.