본문 바로가기

Web/spring

[Spring Framework core] 1.5. Bean Scopes

728x90

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

 

틀린 해석이 있다면 알려주세요 🥖

 

 


 

1.5. Bean Scopes

 

Bean 의 definition 를 생성할 때 해당 Bean definition로 정의 된 클래스의 실제 인스턴스를 생성하기 위한 recipe 를 생성해준다.

definition 이 하나의 레시피라고 생각해야한다. << 이는 클래스와 마찬가지로 하나의 레시피 안에서 많은 객체 인스턴스를 생성할 수 있다는 논리로 이어지기 때문이다

 

특정 bean definition 에서 생성된 객체에 연결되는 다양한 종속성 및 구성 값을 제어할 수 있고 특정 bean definition에서 생성된 객체의 scope를 제어할 수 있다. 이러한 접근 방식은 매우 강력하고 유연한 방법이다. Java class 수준에서 객체의 범위를 굽는 대신 구성을 통해 생성하는 객체의 범위를 선택할 수 있기 때문이다. 여러 범위 중에 하나에 배치되도록 Bean definition을 할 수 있다.

Spring Framework는 6개의 scope를 지원하는데, 이 중 4개는 web-aware ApplicationContext 에서만 사용한다.

custom scope도 가능하다

 

Bean scopesScopeDescription

singleton (Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
prototype Scopes a single bean definition to any number of object instances.
request Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
session Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocket Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

 

thread scope도 사용할 수 있지만 기본적으로 등록되어있지는 않다. 

https://docs.spring.io/spring-framework/docs/6.0.5/javadoc-api/org/springframework/context/support/SimpleThreadScope.html

 

SimpleThreadScope (Spring Framework 6.0.5 API)

resolveContextualObject Description copied from interface: Scope Resolve the contextual object for the given key, if any. E.g. the HttpServletRequest object for key "request". Specified by: resolveContextualObject in interface Scope Parameters: key - th

docs.spring.io

 

 

 

위의 범위에 대해서 나눠 알아본다

 

 

 

 

1.5.1. The Singleton Scope

SingleTon의 경우 하나의 공유 인스턴스만 관리되며, 해당 bean definition 과 일치하는 ID를 가진 빈에 대한 모든 요청은 Container에서 반환되는 특정 빈 인스턴스 하나만을 결과로 갖는다

definition을 정의하고, 그것이 싱글톤으로 scope가 되면 Spirng IoC컨테이너는 해당 bean definition에 의해 정의된 객체의 정확히 하나!! 의 인스턴스를 생성한다. 이 단일 인스턴스는 싱글톤 빈의 캐시에 저장되며, 해당 명명된 빈에 대한 모든 requests and references 에 대해 캐시된 개체를 반환한다.

Spring singleTon bean의 개념은 GoF(Gang of Four) 패턴 책에서 정의하는 싱글톤 패턴과는 다르다.

GoF 싱글톤은 특정 클래스의 인스턴스가 ClassLoader 당 하나만 생성될 수 있도록 개체의 범위를 하드코딩한다

The scope of the Spring singleton is best described as being per-container and per-bean. 

단일 Spring Container에서 특정 클래스에 대해 하나의 빈을 정의하면 Spring container는 해당 빈 정의에 의해 정의된 클래스의 인스턴스를 하나만 생성한다. 싱글톤의 범위는 Spring 의 기본 범위이다. 

 

XML - bean Singleton definition

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

 

 

 

 

1.5.2. The Prototype Scope

The non-singleton prototype scope of bean 의 배치는 특정 bean의 요청이 있을 때마다 새로운 bean 인스턴스를 생성한다

즉, Bean이 다른 Bean에 주입되거나 getBean() container의 메소드 호출로 요청하게 된다. 일반적으로 모든 state full bean에는 이 prototype scope을 사용하며, stateless bean에는 싱글톤 scope를 사용해야한다

 

 

(DAO(Data Access Object)는 일반적으로 프로토타입으로 구성되지않는다. DAO 는 conversational 상태가 없기 때문에 싱글톤 다이어그램의 핵심을 re-use 하는게 낫다.

 

XML로 프로토타입을 정의하면

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

 

 

다른 범위와는 다르게 Spring은 prototype bean의 전체 수명주기를 관리하지않는다. Container는 proto type 개체를 인스턴스화 하고 구성, assemble하여 해당 프로토타입 인스턴스에 대한 추가 Log 없이 클라이언트에게 전달한다. 따라서 범위에 관계 없이 모든 객체에 대해 초기과 생명주기 콜백 메서드가 호출되더라도, 프로토타입의 경우에는 설정된 소멸 생명주기 콜백은 호출되지 않는다.

클라이언트 코드는 프로토타입 범위 객체를 정리하고 프로토타입 빈이 보유하고 있는 값비싼 리소스를 해제해야 합니다. Spring 컨테이너가 프로토타입 범위의 bean이 보유한 리소스를 해제하려면 아래의 bean post process를 사용해야한다

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-extension-bpp

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

 

In some respects, the Spring container’s role in regard to a prototype-scoped bean is a replacement for the Java new operator. All lifecycle management past that point must be handled by the client. (For details on the lifecycle of a bean in the Spring container, see Lifecycle Callbacks.)

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

 

 

 

1.5.3. Singleton Beans with Prototype-bean Dependencies

ingleton-scoped beans with dependencies on prototype beans 를 사용하는 경우에는 인스턴스화 시간에 종속성이 해결된다

프로토타입 범위의 빈을 Singleton scope의 빈에 주입하면, 그 새 프로토타입 빈이 인스턴스화 된 다음 싱글톤 빈에 종속성 주입된다

프로토타입 인스턴스는 싱글톤 범위의 Bean에 제공되는 유일한 인스턴스이다

 

그러나, 싱글톤 범위의 빈이 RunTime 시에 반복적으로 프로토 타입 범위의 빈이 새 인스턴스를 가지려고 한다면, 스프링 컨테이너가 싱글톤빈을 인스턴스화하고 종속성을 해결하고 주입할 때 주입이 한 번만 << 발생하기 때문에 프로토타입 범위의 빈에 종속성을 주입할 수는 없다.

만약 런타임 시 프로토타입의 빈의 새 인스턴스가 두 번이상 필요할 때는 method injention을 사용하면 된다

 

 

1.5.4. Request, Session, Application, and WebSocket Scopes

The request, session, application, and websocket scopes are available only if you use a web-aware Spring ApplicationContext implementation (such as XmlWebApplicationContext). If you use these scopes with regular Spring IoC containers, such as the ClassPathXmlApplicationContext, an IllegalStateException that complains about an unknown bean scope is thrown.

 

 

Initial Web Configuration

To support the scoping of beans at the request, session, application, and websocket levels (web-scoped beans), some minor initial configuration is required before you define your beans. (This initial setup is not required for the standard scopes: singleton and prototype.)

How you accomplish this initial setup depends on your particular Servlet environment.

 

WebSock scope는 그 전에 초기 구성이 필요하다. 

설정을 수행하는 방법은 특정 서블릿 환경에 따라 다르게 구성된다.

 

If you access scoped beans within Spring Web MVC, in effect, within a request that is processed by the Spring DispatcherServlet, no special setup is necessary. DispatcherServlet already exposes all relevant state.

WebMVC의 경우 사실상 DispatcherServlet에서 처리된다.

ServletRequestListener 인터페이스를 사용하여 수행하면 된다.

 

If you use a Servlet web container, with requests processed outside of Spring’s DispatcherServlet (for example, when using JSF), you need to register the org.springframework.web.context.request.RequestContextListener ServletRequestListener. This can be done programmatically by using the WebApplicationInitializer interface. Alternatively, add the following declaration to your web application’s web.xml file:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>
 

Alternatively, if there are issues with your listener setup, consider using Spring’s RequestContextFilter. The filter mapping depends on the surrounding web application configuration, so you have to change it as appropriate. The following listing shows the filter part of a web application:

<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>
 

DispatcherServlet, RequestContextListener, and RequestContextFilter all do exactly the same thing, namely bind the HTTP request object to the Thread that is servicing that request. This makes beans that are request- and session-scoped available further down the call chain.

 

Request Scope

Consider the following XML configuration for a bean definition:

<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
 

The Spring container creates a new instance of the LoginAction bean by using the loginAction bean definition for each and every HTTP request. That is, the loginAction bean is scoped at the HTTP request level. You can change the internal state of the instance that is created as much as you want, because other instances created from the same loginAction bean definition do not see these changes in state. They are particular to an individual request. When the request completes processing, the bean that is scoped to the request is discarded.

When using annotation-driven components or Java configuration, the @RequestScope annotation can be used to assign a component to the request scope. The following example shows how to do so:

@RequestScope
@Component
public class LoginAction {
    // ...
}
 
 
 
 

 

Session Scope

Consider the following XML configuration for a bean definition:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
 

The Spring container creates a new instance of the UserPreferences bean by using the userPreferences bean definition for the lifetime of a single HTTP Session. In other words, the userPreferences bean is effectively scoped at the HTTP Session level. As with request-scoped beans, you can change the internal state of the instance that is created as much as you want, knowing that other HTTP Session instances that are also using instances created from the same userPreferences bean definition do not see these changes in state, because they are particular to an individual HTTP Session. When the HTTP Session is eventually discarded, the bean that is scoped to that particular HTTP Session is also discarded.

When using annotation-driven components or Java configuration, you can use the @SessionScope annotation to assign a component to the session scope.

Java
Kotlin
@SessionScope
@Component
public class UserPreferences {
    // ...
}
 

 

 

Application Scope

Consider the following XML configuration for a bean definition:

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
 

The Spring container creates a new instance of the AppPreferences bean by using the appPreferences bean definition once for the entire web application. That is, the appPreferences bean is scoped at the ServletContext level and stored as a regular ServletContext attribute. This is somewhat similar to a Spring singleton bean but differs in two important ways: It is a singleton per ServletContext, not per Spring ApplicationContext (for which there may be several in any given web application), and it is actually exposed and therefore visible as a ServletContext attribute.

When using annotation-driven components or Java configuration, you can use the @ApplicationScope annotation to assign a component to the application scope. The following example shows how to do so:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
 

 

 

 

WebSocket Scope

WebSocket scope is associated with the lifecycle of a WebSocket session and applies to STOMP over WebSocket applications, see WebSocket scope for more details.

Scoped Beans as Dependencies

The Spring IoC container manages not only the instantiation of your objects (beans), but also the wiring up of collaborators (or dependencies). If you want to inject (for example) an HTTP request-scoped bean into another bean of a longer-lived scope, you may choose to inject an AOP proxy in place of the scoped bean. That is, you need to inject a proxy object that exposes the same public interface as the scoped object but that can also retrieve the real target object from the relevant scope (such as an HTTP request) and delegate method calls onto the real object.

 

 

 

**

You may also use <aop:scoped-proxy/> between beans that are scoped as singleton, with the reference then going through an intermediate proxy that is serializable and therefore able to re-obtain the target singleton bean on deserialization.

When declaring <aop:scoped-proxy/> against a bean of scope prototype, every method call on the shared proxy leads to the creation of a new target instance to which the call is then being forwarded.
Also, scoped proxies are not the only way to access beans from shorter scopes in a lifecycle-safe fashion. You may also declare your injection point (that is, the constructor or setter argument or autowired field) as ObjectFactory<MyTargetBean>, allowing for a getObject() call to retrieve the current instance on demand every time it is needed — without holding on to the instance or storing it separately.
As an extended variant, you may declare ObjectProvider<MyTargetBean> which delivers several additional access variants, including getIfAvailable and getIfUnique.
The JSR-330 variant of this is called Provider and is used with a Provider<MyTargetBean> declaration and a corresponding get() call for every retrieval attempt. See here for more details on JSR-330 overall.
 
 
 

The configuration in the following example is only one line, but it is important to understand the “why” as well as the “how” behind it:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/> 
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>
 
 

The line that defines the proxy.

 

 

To create such a proxy, you insert a child <aop:scoped-proxy/> element into a scoped bean definition (see Choosing the Type of Proxy to Create and XML Schema-based configuration). Why do definitions of beans scoped at the request, session and custom-scope levels require the <aop:scoped-proxy/> element? Consider the following singleton bean definition and contrast it with what you need to define for the aforementioned scopes (note that the following userPreferences bean definition as it stands is incomplete):

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
 

In the preceding example, the singleton bean (userManager) is injected with a reference to the HTTP Session-scoped bean (userPreferences). The salient point here is that the userManager bean is a singleton: it is instantiated exactly once per container, and its dependencies (in this case only one, the userPreferences bean) are also injected only once. This means that the userManager bean operates only on the exact same userPreferences object (that is, the one with which it was originally injected).

This is not the behavior you want when injecting a shorter-lived scoped bean into a longer-lived scoped bean (for example, injecting an HTTP Session-scoped collaborating bean as a dependency into singleton bean). Rather, you need a single userManager object, and, for the lifetime of an HTTP Session, you need a userPreferences object that is specific to the HTTP Session. Thus, the container creates an object that exposes the exact same public interface as the UserPreferences class (ideally an object that is a UserPreferences instance), which can fetch the real UserPreferences object from the scoping mechanism (HTTP request, Session, and so forth). The container injects this proxy object into the userManager bean, which is unaware that this UserPreferences reference is a proxy. In this example, when a UserManager instance invokes a method on the dependency-injected UserPreferences object, it is actually invoking a method on the proxy. The proxy then fetches the real UserPreferences object from (in this case) the HTTP Session and delegates the method invocation onto the retrieved real UserPreferences object.

Thus, you need the following (correct and complete) configuration when injecting request- and session-scoped beans into collaborating objects, as the following example shows:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
 

 

Choosing the Type of Proxy to Create

 

By default, when the Spring container creates a proxy for a bean that is marked up with the <aop:scoped-proxy/> element, a CGLIB-based class proxy is created.

 

CGLIB proxies intercept only public method calls! Do not call non-public methods on such a proxy. They are not delegated to the actual scoped target object.

 

 

Alternatively, you can configure the Spring container to create standard JDK interface-based proxies for such scoped beans, by specifying false for the value of the proxy-target-class attribute of the <aop:scoped-proxy/> element. Using JDK interface-based proxies means that you do not need additional libraries in your application classpath to affect such proxying. However, it also means that the class of the scoped bean must implement at least one interface and that all collaborators into which the scoped bean is injected must reference the bean through one of its interfaces. The following example shows a proxy based on an interface:

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.stuff.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
 

For more detailed information about choosing class-based or interface-based proxying, see Proxying Mechanisms.

 

 

 

1.5.5. Custom Scopes

Bean Scope는 확장할 수 있다. 자기 자신의 scope를 define하거나 재정의할 수도 있지만 재정의의 경우는 현재 할 수 없다. singleton 및 prototype scope 재정의는 불가능하다.

 

 

Creating a Custom Scope

custom scopes를 Spring Container에 사용하려면 org.springframework.beans.factory.config.Scope 의 인터페이스를 구현해야한다. https://docs.spring.io/spring-framework/docs/6.0.5/javadoc-api/org/springframework/beans/factory/config/Scope.html

 

Scope (Spring Framework 6.0.5 API)

resolveContextualObject Resolve the contextual object for the given key, if any. E.g. the HttpServletRequest object for key "request". Parameters: key - the contextual key Returns: the corresponding object, or null if none found Throws: IllegalStateExcepti

docs.spring.io

 

이 인터페이스 Scope는 Scope에서 개체를 가져오고, scope에서 제거하고, 소멸하도록 하는 네가지 metho가 있다

 

session scope implementation은 Session scope bean을 반환한다. (존재하지 않을 경우에는 향후 참조를 위해 Session 에 바인딩 후 새 인스턴스를 반환한다)

 

 

기본 Scope에서 개체 반환

Object get(String name, ObjectFactory<?> objectFactory)

 

 

객체를 반환해야하나 지정된 이름의 객체를 찾을 수 없으면 null을 반환시킨다

Object remove(String name)

 

 

scope가 없어졌거나, scope의 지정 개체가 소멸될 때 호출할 콜백을 등록한다

void registerDestructionCallback(String name, Runnable destructionCallback)

https://docs.spring.io/spring-framework/docs/6.0.5/javadoc-api/org/springframework/beans/factory/config/Scope.html#registerDestructionCallback

 

Scope (Spring Framework 6.0.5 API)

resolveContextualObject Resolve the contextual object for the given key, if any. E.g. the HttpServletRequest object for key "request". Parameters: key - the contextual key Returns: the corresponding object, or null if none found Throws: IllegalStateExcepti

docs.spring.io

 

 

 

기본 범위에 대한 conversation identifier (식별자)를 가져온다

String getConversationId()
 

이 식별자는 범위마다 다르다. 세션 범위 구현의 경우 이 식별자는 세션 식별자일 수 있다.

 

 

 

Using a Custom Scope

하나 이상의 custom scope를 구현하고 작성 후 테스트 했다면 Spring Container가 이 새 범위를 인식하도록 해야한다

 

Spring container에 새 Scope를 등록하는 주 방법

void registerScope(String scopeName, Scope scope);

 

이 method는 ConfigurableBeanFactory 인터페이스에서 선언된다. Spring과 함께 제공되는 대부분의 ApplicationContext 구현에서 BeanFactory property를 통해 사용할 수 있다

registerScope(..) method의 첫번째 argument는 범위와 연결된 고유한 이름이다. Spring Container자체에서 예를 들면 SingleTon, ProtoType 등이 있다.

두번째 argument에서 실제 사용자 지정 범위 구현을 어떻게 하지 구현하면 된다.

 

 

 

 

** SimpleThreadScope는 스프링에 포함되어있지만 기본적으로 scope가 등록되어있지 않다고 가정하고 사용한다

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

 

scope 지정

<bean id="..." class="..." scope="thread">

 

With a custom Scope implementation, you are not limited to programmatic registration of the scope.

You can also do the Scope registration declaratively, by using the CustomScopeConfigurer class, as the following example shows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <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>

    <bean id="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>

 

FactoryBean 구현을 위한 <bean> 선언 내에 <aop:scoped-proxy/>를 배치할 때 getObject()에서 반환된 객체가 아니라 범위가 지정되는 것은 팩토리 빈 자체이다.

728x90