Bean Scopes

2024. 11. 14. 12:32Spring Framework/Spring IoC

Bean Scopes

빈 정의를 생성할 때, 해당 빈 정의에 의해 정의된 클래스의 실제 인스턴스를 생성하기 위한 레시피를 만듭니다. 빈 정의가 레시피라는 생각은 중요한데, 이는 클래스와 마찬가지로 하나의 레시피에서 많은 객체 인스턴스를 생성할 수 있다는 것을 의미합니다.

특정 빈 정의에서 생성된 객체에 주입될 다양한 의존성과 구성 값뿐만 아니라, 특정 빈 정의에서 생성된 객체의 범위를 제어할 수도 있습니다. 이 접근 방식은 강력하고 유연한데, 이는 자바 클래스 수준에서 객체의 범위를 구체화하는 대신 구성을 통해 생성하는 객체의 범위를 선택할 수 있기 때문입니다. 빈은 여러 범위 중 하나로 정의될 수 있습니다. Spring 프레임워크는 여섯 가지 범위를 지원하며, 이 중 네 가지는 web-aware ApplicationContext를 사용할 경우에만 사용할 수 있습니다. 또한 custom scope (사용자 정의 범위)생성할 수도 있습니다.

다음 테이블은 지원되는 범위를 설명합니다:

Table 1. Bean scopesScopeDescription

Scope Description
singleton (Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
각 Spring IoC 컨테이너마다 단일 빈 정의를 단일 객체 인스턴스로 범위 지정합니다.
prototype Scopes a single bean definition to any number of object instances.
단일 Bean 정의의 범위를 원하는 수의 객체 인스턴스로 지정합니다.
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.
단일 HTTP 요청의 라이프사이클에 대해 단일 Bean 정의의 범위를 지정합니다. 즉, 각 HTTP 요청에는 단일 Bean 정의 뒤에 생성된 자체 Bean 인스턴스가 있습니다. 웹 인식 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.
단일 Bean 정의의 범위를 HTTP 세션의 라이프사이클로 지정합니다. 웹 인식 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.
단일 Bean 정의의 범위를 ServletContext의 라이프사이클로 지정합니다. 웹 인식 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.
단일 Bean 정의의 범위를 WebSocket의 라이프사이클로 지정합니다. 웹 인식 Spring ApplicationContext의 컨텍스트에서만 유효합니다.

 

스레드 범위를 사용할 수 있지만 기본적으로 등록되지 않습니다. 자세한 내용은 SimpleThreadScope 설명서를 참조하세요. 이 범위 또는 다른 사용자 정의 범위를 등록하는 방법에 대한 지침은 사용자 정의 Scope 사용을 참조하세요.

 

The Singleton Scope

싱글톤 빈의 경우 단 하나의 공유 인스턴스만 관리되며, 해당 빈 정의와 일치하는 ID 또는 IDs를 가진 빈에 대한 모든 요청은 Spring 컨테이너에 의해 특정한 그 하나의 빈 인스턴스가 반환됩니다.

다른 방식으로 설명하자면, 빈 정의를 정의하고 그것을 싱글톤으로 범위 지정할 때, Spring IoC 컨테이너는 해당 빈 정의에 의해 정의된 객체의 정확히 하나의 인스턴스를 생성합니다. 이 단일 인스턴스는 싱글톤 빈의 캐시에 저장되며, 그 명명된 빈에 대한 모든 후속 요청 및 참조는 캐시된 객체를 반환합니다. 다음 이미지는 싱글톤 범위가 어떻게 작동하는지 보여줍니다:

 

Spring의 싱글톤 빈 개념은 GoF(Gang of Four) 패턴 책에 정의된 싱글톤 패턴과 다릅니다. GoF 싱글톤은 ClassLoader당 하나의 특정 클래스 인스턴스만 생성되도록 객체의 범위를 하드코딩합니다. Spring 싱글톤의 범위는 컨테이너별 및 Bean별 범위로 가장 잘 설명됩니다. 이는 단일 Spring 컨테이너의 특정 클래스에 대해 하나의 Bean을 정의하면 Spring 컨테이너는 해당 Bean 정의에 의해 정의된 클래스의 인스턴스를 하나만 생성한다는 의미입니다. 싱글톤 범위는 Spring의 기본 범위입니다. Bean을 XML의 싱글톤으로 정의하려면 다음 예제와 같이 Bean을 정의할 수 있습니다.

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

 

The Prototype Scope

비싱글톤 프로토타입 범위의 빈 배포는 해당 특정 빈에 대한 요청이 있을 때마다 새로운 빈 인스턴스를 생성합니다. 즉, 다른 빈에 빈이 주입되거나 컨테이너의 getBean() 메소드 호출을 통해 요청할 때입니다. 원칙적으로, 상태를 가진 모든 빈에는 프로토타입 범위를 사용하고, 상태가 없는 빈에는 싱글톤 범위를 사용해야 합니다.

다음 다이어그램은 Spring 프로토타입 범위를 설명합니다:
 
(데이터 액세스 객체(DAO)는 일반적으로 대화형 상태를 보유하지 않기 때문에 프로토타입으로 설정되지 않습니다. 싱글톤 다이어그램의 핵심을 재사용하는 것이 더 쉬웠습니다.)
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class AppConfig {

    @Bean
    @Scope("prototype")
    public DefaultAccountService accountService() {
        return new DefaultAccountService();
    }
}

 

다른 스코프와 달리, Spring은 프로토타입 빈의 전체 생명주기를 관리하지 않습니다. 컨테이너는 프로토타입 객체를 인스턴스화하고, 구성하며, 그 외에 조립한 후 클라이언트에게 전달하지만, 그 프로토타입 인스턴스의 추가적인 기록은 남기지 않습니다. 따라서, 초기화 생명주기 콜백 메소드가 범위에 관계없이 모든 객체에 대해 호출되지만, 프로토타입의 경우에는 구성된 소멸 생명주기 콜백이 호출되지 않습니다. 클라이언트 코드는 프로토타입 범위의 객체를 정리하고 프로토타입 빈이 보유한 고가의 자원을 해제해야 합니다. 프로토타입 범위의 빈이 보유한 자원을 Spring 컨테이너가 해제하도록 하려면, 정리가 필요한 빈에 대한 참조를 보유하는 bean post-processor 를 사용해 보세요.

 

어떤 면에서, Spring 컨테이너가 프로토타입 스코프 빈과 관련하여 수행하는 역할은 자바의 new 연산자를 대체하는 것입니다. 그 지점 이후의 모든 생명주기 관리는 클라이언트에 의해 처리되어야 합니다. (Spring 컨테이너에서 빈의 생명주기에 대한 자세한 내용은 Lifecycle Callbacks 을 참조하세요.)

 

스프링 빈 라이프사이클 콜백 메소드 예:

import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy;

public class MyBean {

    // 빈이 생성되고, 의존성 주입이 완료된 후 호출되는 메소드
    @PostConstruct
    public void init() {
        // 초기화 로직
        System.out.println("MyBean is initialized");
    }

    // 빈이 소멸되기 전에 호출되는 메소드
    @PreDestroy
    public void destroy() {
        // 정리 로직
        System.out.println("MyBean is destroyed");
    }
}
@Configuration
public class AppConfig {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

 

Singleton Beans with Prototype-bean Dependencies

싱글톤 스코프 빈이 프로토타입 빈에 의존하는 경우, 의존성은 인스턴스 생성 시에 해결된다는 점을 인지해야 합니다. 따라서 싱글톤 스코프 빈에 프로토타입 스코프 빈을 의존성 주입할 경우, 새로운 프로토타입 빈이 인스턴스화되고 그 후 싱글톤 빈에 의존성 주입됩니다. 이 프로토타입 인스턴스는 싱글톤 스코프 빈에 공급되는 유일한 인스턴스입니다.

하지만, 싱글톤 스코프 빈이 런타임에 반복적으로 프로토타입 스코프 빈의 새 인스턴스를 획득하길 원한다고 가정해 봅시다. 싱글톤 빈에 프로토타입 스코프 빈을 의존성 주입할 수는 없습니다. 왜냐하면 그 주입은 오직 한 번, 즉 Spring 컨테이너가 싱글톤 빈을 인스턴스화하고 그 의존성을 해결하고 주입할 때만 발생하기 때문입니다. 런타임에 프로토타입 빈의 새 인스턴스가 한 번 이상 필요한 경우, Method Injection 을 참조하십시오.

 

Request, Session, Application, and WebSocket Scopes

request, session, application, websocket 스코프는 웹 인식 Spring ApplicationContext 구현(예: XmlWebApplicationContext)을 사용하는 경우에만 사용할 수 있습니다. ClassPathXmlApplicationContext와 같은 일반 Spring IoC 컨테이너에서 이러한 스코프를 사용하려고 하면 알 수 없는 빈 스코프에 대한 IllegalStateException이 발생합니다.

 

Initial Web Configuration

request, session, application, websocket 레벨에서 빈을 스코핑(웹 스코프 빈)하려면, 빈을 정의하기 전에 약간의 초기 구성이 필요합니다. (표준 스코프인 singleton과 prototype에는 이 초기 설정이 필요하지 않습니다.)

이 초기 설정을 수행하는 방법은 특정 Servlet 환경에 따라 달라집니다.

Spring Web MVC 내에서, 즉 Spring DispatcherServlet이 처리하는 요청 내에서 스코프 빈에 접근하는 경우에는 특별한 설정이 필요하지 않습니다. DispatcherServlet은 이미 모든 관련 상태를 노출합니다.

Spring의 DispatcherServlet 외부에서 요청을 처리하는 Servlet 웹 컨테이너를 사용하는 경우(JSF 사용 시 등)에는 org.springframework.web.context.request.RequestContextListener ServletRequestListener를 등록해야 합니다. 이는 WebApplicationInitializer 인터페이스를 사용하여 프로그래밍 방식으로 수행할 수 있습니다. 또는, 다음 선언을 웹 애플리케이션의 web.xml 파일에 추가하십시오.

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

 

또한, 리스너 설정에 문제가 있는 경우 Spring의 RequestContextFilter를 사용하는 것을 고려해 볼 수 있습니다. 필터 매핑은 주변 웹 애플리케이션 구성에 따라 달라지므로 상황에 맞게 변경해야 합니다. 다음은 웹 애플리케이션의 필터 부분을 보여줍니다.

<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, RequestContextFilter는 모두 동일한 작업을 수행하며, 즉 요청을 처리하는 스레드에 HTTP request 객체를 바인딩합니다. 이를 통해 요청 및 세션 스코프의 빈을 호출 체인 하위에서 사용할 수 있게 됩니다.

 

Request scope

다음은 빈 정의를 위한 XML 구성 예시입니다:

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

 

Spring 컨테이너는 각 HTTP 요청마다 loginAction 빈 정의를 사용하여 LoginAction 빈의 새 인스턴스를 생성합니다. 즉, loginAction 빈은 HTTP 요청 레벨로 스코핑됩니다. 생성된 인스턴스의 내부 상태는 원하는 만큼 변경할 수 있으며, 동일한 loginAction 빈 정의에서 생성된 다른 인스턴스들은 이러한 상태 변경을 인식하지 않습니다. 이 상태는 개별 요청에 고유한 것입니다. 요청 처리가 완료되면, 요청에 스코핑된 빈은 폐기됩니다.

애너테이션 기반 컴포넌트나 Java 구성을 사용할 때, @RequestScope 애너테이션을 사용하여 컴포넌트를 request 스코프로 할당할 수 있습니다. 다음 예시는 이를 구현하는 방법을 보여줍니다.

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

 

Session Scope다음은 빈 정의를 위한 XML 구성 예시입니다:

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

 

Spring 컨테이너는 단일 HTTP 세션의 라이프타임 동안 userPreferences 빈 정의를 사용하여 UserPreferences 빈의 새 인스턴스를 생성합니다. 즉, userPreferences 빈은 HTTP 세션 레벨로 스코핑됩니다. request 스코프 빈과 마찬가지로 생성된 인스턴스의 내부 상태를 원하는 만큼 변경할 수 있으며, 동일한 userPreferences 빈 정의에서 생성된 인스턴스를 사용하는 다른 HTTP 세션 인스턴스들은 이러한 상태 변경을 인식하지 않습니다. 이는 개별 HTTP 세션에 고유한 것입니다. HTTP 세션이 결국 폐기되면, 해당 HTTP 세션에 스코핑된 빈도 함께 폐기됩니다.

 

애너테이션 기반 컴포넌트나 Java 구성을 사용할 때, @SessionScope 애너테이션을 사용하여 컴포넌트를 세션 스코프로 할당할 수 있습니다.

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

 

Application Scope

다음은 빈 정의를 위한 XML 구성 예시입니다:

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

 

Spring 컨테이너는 전체 웹 애플리케이션에 대해 한 번만 appPreferences 빈 정의를 사용하여 AppPreferences 빈의 새 인스턴스를 생성합니다. 즉, appPreferences 빈은 ServletContext 수준으로 스코핑되며 일반 ServletContext 속성으로 저장됩니다. 이는 Spring 싱글톤 빈과 다소 유사하지만 두 가지 중요한 차이점이 있습니다. ServletContext당 하나의 싱글톤이라는 점과, 실제로 ServletContext 속성으로 노출되어 가시성을 갖는다는 점입니다.

애너테이션 기반 컴포넌트나 Java 구성을 사용할 때, @ApplicationScope 애너테이션을 사용하여 컴포넌트를 애플리케이션 스코프로 할당할 수 있습니다. 다음 예시는 이를 구현하는 방법을 보여줍니다.

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

 

WebSocket Scope

WebSocket 스코프는 WebSocket 세션의 생명 주기와 연관되어 있으며, STOMP over WebSocket 애플리케이션에 적용됩니다. 자세한 내용은 WebSocket 스코프를 참조하십시오.

 

Scoped Beans as DependenciesSpring IoC 컨테이너는 객체(빈)의 인스턴스화뿐만 아니라 협력 객체(또는 의존성)의 연결도 관리합니다. 예를 들어, HTTP 요청 스코프 빈을 더 오래 지속되는 스코프의 다른 빈에 주입하려면, 스코프 빈 대신 AOP 프록시를 주입하는 방식을 선택할 수 있습니다. 즉, 스코프 객체와 동일한 public 인터페이스를 노출하면서도 실제 타겟 객체를 관련 스코프(예: HTTP 요청)에서 가져와 메서드 호출을 실제 객체에 위임할 수 있는 프록시 객체를 주입해야 합니다.

또한, <aop:scoped-proxy/>를 싱글톤으로 스코핑된 빈 사이에 사용하여, 참조가 직렬화가 가능한 중간 프록시를 통해 전달되도록 할 수 있으며, 이로 인해 역직렬화 시 타겟 싱글톤 빈을 다시 획득할 수 있습니다.
스코프가 프로토타입인 빈에 대해 <aop:scoped-proxy/>를 선언할 때는 공유된 프록시의 모든 메서드 호출이 새로운 대상 인스턴스를 생성하여 호출이 해당 인스턴스로 전달됩니다.
또한, 짧은 스코프의 빈에 안전하게 접근하는 방법으로 스코프 프록시만 있는 것은 아닙니다. 생성자나 세터 아규먼트 또는 자동 주입 필드 등 주입 지점을 ObjectFactory<MyTargetBean>으로 선언하여 필요한 매번 getObject() 호출을 통해 현재 인스턴스를 요청 시에 가져올 수 있습니다. 이렇게 하면 인스턴스를 유지하거나 별도로 저장할 필요가 없습니다.
확장된 변형으로, ObjectProvider<MyTargetBean>을 선언할 수 있으며, 이는 getIfAvailablegetIfUnique를 포함한 여러 추가 접근 방식을 제공합니다.
JSR-330 변형은 Provider라고 하며, Provider<MyTargetBean> 선언과 해당 get() 호출로 매번 인스턴스를 가져옵니다. JSR-330에 대한 전체 내용은 여기에서 확인할 수 있습니다.

 

다음 예시의 구성은 한 줄에 불과하지만, 이를 설정하는 이유방법을 이해하는 것이 중요합니다.

<?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/>    (1)
	</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>

 

(1) 해당 라인은 프록시를 정의합니다.

 

이러한 프록시를 생성하려면 스코핑된 빈 정의에 <aop:scoped-proxy/> 자식 엘리먼트를 삽입합니다 (프록시 유형 선택 및 XML 스키마 기반 구성 참조).

일반적인 시나리오에서 request, session, custom-scope 레벨로 스코핑된 빈 정의에 <aop:scoped-proxy/> 엘리먼트가 필요한 이유는 무엇일까요? 다음의 싱글톤 빈 정의를 살펴보고, 앞서 언급한 스코프에 대해 정의해야 할 내용을 비교해 보십시오(아래의 userPreferences 빈 정의는 아직 완성되지 않았다는 점에 유의하십시오).

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

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

 

위 예시에서 싱글톤 빈(userManager)은 HTTP 세션 스코프 빈(userPreferences)에 대한 참조를 주입받습니다. 여기서 중요한 점은 userManager 빈이 싱글톤이라는 것입니다. 즉, 컨테이너당 정확히 한 번 인스턴스화되며, 의존성(이 경우 userPreferences 빈)은 한 번만 주입됩니다. 이는 userManager 빈이, 처음 주입된 동일한 userPreferences 객체만을 사용하게 된다는 의미입니다.

짧은 수명의 스코프 빈을 더 긴 수명의 스코프 빈에 주입할 때(예: 싱글톤 빈에 HTTP 세션 스코프의 협력 빈을 의존성으로 주입할 때)는 이와 같은 동작이 원하는 바가 아닙니다. 이 경우에는 하나의 userManager 객체가 필요하며, HTTP 세션의 라이프타임 동안 해당 세션에 특화된 userPreferences 객체가 필요합니다. 따라서 컨테이너는 UserPreferences 클래스와 동일한 public 인터페이스를 노출하는 객체(이상적으로는 UserPreferences 인스턴스)를 생성하여, HTTP 요청, 세션 등 스코핑 메커니즘에서 실제 UserPreferences 객체를 가져와 메서드 호출을 위임할 수 있도록 합니다. 컨테이너는 이 프록시 객체를 userManager 빈에 주입하며, userManager 빈은 이 UserPreferences 참조가 프록시라는 것을 인식하지 못합니다. 이 예시에서 UserManager 인스턴스가 의존성으로 주입된 UserPreferences 객체의 메서드를 호출할 때, 실제로는 프록시의 메서드를 호출하게 됩니다. 그러면 프록시는 (이 경우) HTTP 세션에서 실제 UserPreferences 객체를 가져와 메서드 호출을 해당 실제 객체로 위임합니다.

따라서 요청 및 세션 스코프 빈을 협력 객체에 주입할 때는 다음 예시와 같이 올바르고 완전한 구성이 필요합니다.

<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디폴트로 Spring 컨테이너가 <aop:scoped-proxy/> 엘리먼트가 표시된 빈에 대해 프록시를 생성할 때, CGLIB 기반 클래스 프록시가 생성됩니다.

CGLIB 프록시는 private 메서드를 가로채지 않습니다. 따라서 이러한 프록시에서 private 메서드를 호출하려고 하면 실제 스코프 타겟 객체에 위임되지 않습니다.

 

또한, <aop:scoped-proxy/> 엘리먼트의 proxy-target-class 속성 값을 false로 지정하여 Spring 컨테이너가 이러한 스코프 빈에 대해 표준 JDK 인터페이스 기반 프록시를 생성하도록 설정할 수 있습니다. JDK 인터페이스 기반 프록시를 사용하면 애플리케이션 클래스 경로에 추가 라이브러리가 필요하지 않습니다. 그러나, 이는 스코프 빈 클래스가 최소 하나의 인터페이스를 구현해야 하며, 스코프 빈이 주입되는 모든 협력 객체들이 해당 인터페이스 중 하나를 통해 빈을 참조해야 함을 의미합니다. 다음 예시는 인터페이스를 기반으로 한 프록시를 보여줍니다.

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

 

클래스 기반 프록시와 인터페이스 기반 프록시 선택에 대한 자세한 정보는 [프록시 메커니즘](Proxying Mechanisms)을 참조하십시오.

 

Injection Request/Session References Directly

factory 스코프의 대안으로, Spring WebApplicationContext는 HttpServletRequest, HttpServletResponse, HttpSession, WebRequest 및 (JSF가 있는 경우) FacesContext와 ExternalContext를 Spring에서 관리하는 빈에 주입하는 것을 지원합니다. 이러한 객체들은 다른 빈에 대한 일반적인 주입 지점과 마찬가지로 타입 기반 자동 주입을 통해 간단히 주입할 수 있습니다. Spring은 일반적으로 요청 및 세션 객체에 대해 프록시를 주입하므로, 이는 싱글톤 빈 및 직렬화 가능한 빈에서도 작동하는 이점을 제공하며, 이는 팩토리 스코프 빈에 대한 스코프 프록시와 유사합니다.

 

Custom Scopes

생략...