https://sundaland.tistory.com/472
▶ Bean Scopes
빈 정의를 생성할 때, 해당 빈 정의에 의해 정의된 클래스의 실제 인스턴스를 생성하기 위한 레시피를 만든다. 빈 정의가 레시피라고 하는 이유는, 클래스와 마찬가지로 하나의 레시피에서 많은 객체 인스터스를 생성할 수 있다는 것을 의미하기 때문이다.
특정 빈 정의에서 생성된 객체에 주입될 다양한 의존성과 구성 값 뿐만이 아니라, 특정 빈 정의에서 생성된 객체의 범위를 제어할 수도 있다. 이 접근 방식은 강력하고 유연한데, 이는 자바 클래스 수준에서 객체의 범위를 구체화하는 대신 구성을 통해 생성하는 객체의 범위를 선택할 수 있기 때문이다. 빈은 여러 범위 중 하나로 정의될 수 있다. 스프링 프레임워크는 여섯 가지 범위를 지원하며, 이 중 네 가지는 web-aware ApplicationContext를 사용할 경우에만 사용할 수 있다. 또한 custom scope (사용자 정의 범위)를 생성할 수도 있다.
아래의 테이블은 지원되는 범위를 설명한다.
Scope (범위) | 설명 |
singleton | 각 Spring IoC 컨테이너마다 단일 빈 정의를 단일 객체 인스턴스로 범위 지정합니다. |
prototype | 단일 Bean 정의의 범위를 원하는 수의 객체 인스턴스로 지정합니다. |
request | 단일 HTTP 요청의 라이프사이클에 대해 단일 Bean 정의의 범위를 지정합니다. 즉, 각 HTTP 요청에는 단일 Bean 정의 뒤에 생성된 자체 Bean 인스턴스가 있습니다. 웹 인식 Spring ApplicationContext의 컨텍스트에서만 유효합니다. |
session | 단일 Bean 정의의 범위를 HTTP 세션의 라이프사이클로 지정합니다. 웹 인식 Spring ApplicationContext의 컨텍스트에서만 유효합니다. |
application | 단일 Bean 정의의 범위를 ServletContext의 라이프사이클로 지정합니다. 웹 인식 Spring ApplicationContext의 컨텍스트에서만 유효합니다. |
websocket | 단일 Bean 정의의 범위를 WebSocket의 라이프사이클로 지정합니다. 웹 인식 Spring ApplicationContext의 컨텍스트에서만 유효합니다. |
스레드 범위를 사용할 수 있지만 기본적으로 등록되지 않는다.
▷ The Singleton Scope
싱글톤 빈의 경우 단 하나의 공유 인스턴스만 관리되며, 해당 빈 정의와 일치하는 ID 또는 IDs를 가진 빈에 대한 모든 요청은 스프링 컨테이너에 의해 특정한 그 하나의 빈 인스턴스가 반환된다.
빈 정의를 정의하고 그것을 싱글톤으로 범위 지정할 때, 스프링 IoC 컨테이너는 해당 빈 정의에 의해 정의된 객체의 하나의 인스턴스를 생성한다. 이 단일 인스턴스는 싱글톤 빈의 캐스에 저장되며, 그 명명된 빈에 대한 모든 후속 요청 및 참조는 캐시된 객체를 반환한다.
스프링의 싱글톤 빈 개념은 GoF (Gang of Four) 패턴 책에 정의된 싱글톤 패턴과 다르다. GoF 싱글톤은 ClassLoader 당 하나의 특정 클래스 인스턴스만 생성되도록 객체의 범의를 하드코딩한다. 스프링 싱글톤의 범위는 컨테이너별 및 Bean 별 범위로 가장 잘 설명된다. 이는 단일 스프링 컨테이너의 특정 클래스에 대해 하나의 Bean을 정의하면 스프링 컨테이너는 해당 Bean 정의에 의해 정의된 클래스의 인스턴스를 하나만 생성한다는 의미이다. 싱글톤의 범위는 스프링의 기본 범위이다. 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() 메서드 호출을 통해 요청할 때를 말한다. 원칙적으로, 상태를 가진 모든 빈에는 프로토타입 범위를 사용하고, 상태가 없는 빈에는 싱글톤 범위를 사용해야 한다.
데이터 액세스 객체 (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();
}
}
다른 스코프와 달리, 스프링은 프로토타입 빈의 전체 생명주기를 관리하지 않는다. 컨테이너는 프로토타입 객체를 인스턴스화하고, 구성하며, 그 외에 조립한 후 클라이언트에게 전달하지만, 그 프로토타입 인스턴스의 추가적인 기록은 남기지 않는다. 따라서, 초기화 생명주기 콜백 메서드가 범위에 관계없이 모든 객체에 대해 호출된지만, 프로토타입의 경우에는 구성된 소멸 생명주기 콜백이 호출되지 않는다. 클라이언트 코드는 프로토타입 범위의 객체를 정리하고 프로토타입 빈이 보유한 고가의 자원을 해제해야 한다. 프로토토압 범위의 빈이 보유한 자원을 스프링 컨테이너가 해제하도록 하려면, 정리가 필요한 빈에 대한 참조극 보유하는 bean post-processor를 사용하는 것이 좋다.
어떤 면에서, 스프링 컨테이너가 프로토타입 스코프 빈과 관련하여 수행하는 역할은 자바의 new 연산자를 대체하는 것이다. 그 지점 이후의 모든 생명주기 관리는 클라이언트에 의해 처리되어야 한다.
▼ 스프링 빈 라이프사이클 콜백 메서드
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 Dependecies
싱글톤 스코프 빈이 포로토타입 빈에 의존하는 경우, 의존성은 인스턴스 생성 시에 해결된다는 점을 인지해야 한다. 따라서 싱글톤 스코프 빈에 프로토타입 스코프 빈을 의존성 주입할 경우, 새로운 프로토타입 빈이 인스턴스화되고 그 후 싱글톤 빈에 의존성이 주입된다. 이 프로토타입 인스턴스는 싱글톤 스코프 빈에 공급되는 유일한 인스턴스다.
하지만, 싱글톤 스코프 빈이 런타임에 반복적으로 프로토타입 스코프 빈의 새 인스턴스를 획득하길 원한다고 가정한다. 싱글톤 빈에 프로토타입 스코프 빈을 의존성 주입할 수는 없다. 왜냐하면 그 주입은 오직 한 번, 즉 스프링 컨테이너가 싱글톤 빈을 인스턴스화하고 그 의존성을 해결하고 주입할 때만 발생하기 때문이다. 런타임에 프로토타입 빈의 새 인스턴스가 한번 이상 필요한 경우, Method Injection을 참조하면 된다.
▷ Request, Session, Application, and WebSocket Scopes
request, session, application, webocket 스코프는 웹 인식 스프링 ApplicationContext 구현 (XmlWebApplicationContext)을 사용하는 경우에만 사용할 수 있다. ClassPathXmlApplicationContext와 같은 일반 스프링 IoC 컨테이너에서 이러한 스코프를 사용하려고 하면 알 수 없는 빈 스코프에 대한 IllegalSateException이 발생한다.
△ Initial Web Configuration
request, session, application, webocket 레벨에서 빈을 스코핑 (웹 스코핑 빈) 하려면, 빈을 정의하기 전에 약간의 초기 구성이 필요하다. (표준 스코프인 singleton과 protype에는 이 초기 설정이 필요하지 않다.)
이 초기 설정을 수행하는 방법은 특정 Servlet 환경에 따라 달라진다.
스프링 Web MVC 내에서, 즉 스프링 DispatcherServlet이 처리하는 요청 내에서 스코프 빈에 접근하는 경우에는 특별한 설정이 필요하지 않다. DispatchServlet은 이미 모든 관련 상태를 노출한다.
스프링의 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>
또한, 리스너 설정에 문제가 있는 경우 스프링의 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"/>
스프링 컨테이너는 각 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"/>
스프링 컨테이너는 단일 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"/>
스프링 컨테이너는 전체 웹 애플리케이션에 대해 한 번만 appPreferences 빈 정의를 사용하여 AppPreferencs 빈의 새 인스턴스를 생성한다. 즉, appPreferences 빈은 ServletContext 수준으로 스코핑되며 일반 ServletContext 속성으로 저장된다. 이는 스프링 싱글톤 빈과 다소 유사하지만 두 가지 중요한 차이점이 있다. ServletContext당 하나의 싱글톤이라는 점과, 실제로 ServletContext 속성으로 노출되어 가시성을 갖는다는 점이다.
어노테이션 기반 컴포넌트나 자바 구성을 사용할 때, @ApplicationScope 어노테이션을 사용하여 컴포넌트를 애플리케이션 스코프로 할당할 수 있다.
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
△ WebSocket Scope
WebSocket 스코프는 WebSocket 세션의 생명 주기와 연관되어 있으며, STOMP (Simple Text Oriented Messaging Protocol) over WebSocket 애플리케이션에 적용된다.
Scoped Beans as DependenciesSpring IoC 컨테이너는 객체 (빈)의 인스턴스화뿐만이 아니라 협력 객체 (또는 의존성)의 연결도 관리한다. 예를 들어, HTTP 요청 스코프 빈을 더 오래 지속되는 스코프의 다른 빈에 주입하려면, 스코프 빈 대신 AOP 프록시를 주입하는 방식을 선택할 수 있다. 즉, 스코프 객체와 동일한 public 인터페이스를 노출하면서도 실제 타겟 객체를 관련 스코프에서 가져와 메서드 호출을 실제 객체에 위암할 수 있는 프록시 갹체를 주입해야 한다.
또한, <aop:scoped-proxy/>를 싱글톤으로 스코피오딘 빈 사이에 사용하여, 참조가 직렬화가 가능한 중간 프록시를 통해 전달되도록 할 수 있으며, 이로 인해 역직렬화시 타겟 싱글톤 빈을 다시 획득할 수 잇다.
스코프가 프로토타입인 빈에 대해 <aop:scoped-porxy/>를 선언할 때는 공유된 프록시의 모든 메서드 호출이 새로운 대상 인스턴스를 생성하여 호출이 해당 인스턴스로 전달된다.
또한, 짧은 스코프의 빈에 안전하게 접근하는 방법으로 스코프 프록시만 있는 것은 아니다. 생성자나 세터 아규먼트 또는 자동 주입 필드 등 주입 지점을 ObjectFactory<MyTargetBean>으르 선언하여 필여한 매번 getObject() 호출을 통해 현재 인스턴스를 요청 시에 가져올 수 있다. 이렇게 하면 인스턴스를 유지하거나 별도로 저장할 필요가 없다.
확장된 변형으로, ObjectFactory<MyTargetBean>을 선언할 수 있으며, 이는 getIfAvailalbe 및 getIfUnique를 포함한 여러 추가 접근 방식을 제공한다.
JSR-330 변형은 Provider라고 하며, Provider<MyTargetBean> 선언과 헤당 get() 호출로 매번 인스턴스를 가져온다.
<?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 스키마 기반 구성 참조)
<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 빈은 싱글톤이다. 즉, 컨테이너당 정확히 한 번만 인스턴스화되며, 의존성 (userPrefernces 빈)은 한 번만 주입된다. 이는 userManager 빈이, 처음 주입된 동일한 userPreferencs 객체만을 사용하게 된다는 의미이다.
짧은 수명의 스코프 빈을 더 긴 수명의 스코프 빈에 주입할 때 (싱글톤 빈에 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>
△ Injection Request/Session References Directly
factory 스코프의 대안으로, 스프링 WebApplicationContext는 HttpServletRequest, HttpServletResponse, HttpSession, WebRequest 및 (JSF가 있는 경우) FacesContext와 ExternalContex 를 스프링에서 관리하는 빈에 주입하는 것을 지원한다. 이러한 객체들은 다른 빈에 대한 일반적인 주입 지점과 마찬가지로 타입 기반 자동 주입을 통해 간단히 주입할 수 있다. 스프링은 일반적으로 요청 및 세션 객체에 대해 프록시를 주입하므로, 이는 싱글톤 빈 및 직렬화 가능한 빈에서도 작동하는 이점을 제공하며, 이는 팩토리 스코프 빈에 대한 스코프 프록시와 유사하다.
'스프링 프레임워크 > IoC (Inversion of Control)' 카테고리의 다른 글
Using the @Configuration annotation (0) | 2024.11.19 |
---|---|
Using the @Bean Annotation (0) | 2024.11.19 |
Instantiating the Spring Container by Using AnnotationConfigApplicationContext (0) | 2024.11.19 |
Fine-tuning Annotation-based Autowiring with Qualifiers (0) | 2024.11.19 |
Annotation-based Container Configuration (0) | 2024.11.19 |