728x90
들어가기에 앞서, 해당 문서는 스프링 공식 문서를 번역한 포스트 입니다.
해당 문서에서는 XML 기반으로 빈 설정을 하고 있는데 이는 어노테이션 기반으로도, 자바코드로도 설정 할 수 있습니다.
개인적으로 공부 할 요량으로 한 포스팅이다 보니 번역 상태가 굉장히 좋지 않습니다.
가능하시다면 원문을 읽으시는것을 추천 드립니다.
- 빈 정의를 생성한다는 것은, 클래스의 실제 인스턴스를 만드는 방법을 알려주는 일종의 레시피를 만드는 것과 같다고 할 수 있습니다.
- 빈 정의를 일종의 레시피로 본다는 것은 우리가 레시피 하나만 있으면 여러 객체의 인스턴스를 만들 수 있다는 점에서 굉장히 중요합니다.
- 객체 생성에 필요한 의존성이나 설정들을 제어하는 것 이외에도 우린 빈 정의(Bean Definition)를 통해 해당 객체의 스코프(범위) 역시 제어할 수 있습니다.
- 객체의 스코프를 빈 정의에서 설정할 수 있기 때문에 자바 클래스 레벨에서 이를 설정 할 필요가 없어지게 됩니다.
- 스프링 프레임워크에서 지원하는 스코프는 총 6개가 있습니다.
- 그 중 4개는 ApplicationContext를 사용해야만 사용 가능합니다.
Scope | Description |
singleton | (기본값) 해당 빈이 하나의 Spring IoC Container당 하나의 객체 인스턴스를 생성하도록 설정합니다. |
prototype | 해당 빈이 여려개의 객체 인스턴스를 생성하도록 설정합니다. |
request | 해당 빈이 하나의 HTTP Request가 들어오면 객체 인스턴스를 생성하도록 설정합니다. 각각의 HTTP Request는 자신만의 객체 인스턴스를 가지게 됩니다. web-aware 스프링 ApplicationContext가 필요합니다. |
session | request와 비슷하지만 해당 HTTP Session 마다 객체 인스턴스를 생성합니다. web-aware 스프링 ApplicationContext가 필요합니다. |
application | request와 비슷하지만 ServletContext마다 객체 인스턴스를 생성합니다. web-aware 스프링 ApplicationContext가 필요합니다. |
websocket | request와 비슷하지만 WebSocket마다 객체 인스턴스를 생성합니다. web-aware 스프링 ApplicationContext가 필요합니다. |
thread 스코프도 사용할 수 있지만 기본적으로 등록되어있지 않습니다.
더 자세히 알고 싶으시다면 SimpleThreadScope를 확인해주세요.
SimpleThreadScope, 혹은 커스텀 스코프를 등록하고자 하신다면 커스텀 스코프 사용하기를 참고해주세요.
The Singleton Scope
- 단 하나의 싱글턴 빈 인스턴스만 생성되고 관리 됩니다.
- 빈 요청이 들어오면 요청된 빈 ID를 확인하여 매칭되는 빈을 스프링 컨테이너가 반환해줍니다.
- 즉, 어떤 빈의 스코프를 싱글턴으로 설정시, 스프링 IoC 컨테이너는 해당 빈의 인스턴스를 단 하나만 생성합니다.
- 이런 싱글 인스턴스는 싱글턴 빈 캐시에 저장되고, 후속 요청이나 참조 발생시 캐시된 빈을 반환합니다.
- 스프링의 싱글턴은 Gang of Four(GoF)의 디자인 패턴에서 말하는 싱글턴과는 다릅니다.
- GoF에서의 싱글턴은 ClassLoader당 클래스의 객체가 단 하나의 인스턴스만 생성되도록 스코프를 하드코딩하는 방식이라면, 스프링에서는 컨테이너당, Bean당 하나만 생성됩니다.
- 따라서 만약 하나의 스프링 컨테이너에서 특정 클래스의 빈을 정의했다면, 스프링 컨테이너는 해당 클래스의 인스턴스를 오직 하나만 만듭니다.
The Prototype Scope
- 싱글턴이 아닌 프로토타입으로 설정된 빈은 해당 빈에 요청이 들어올때마다 새로운 빈 인스턴스를 생성합니다.
- 컨테이너에서 getBean() 메서드를 통해 매번 새로운 빈을 받아 다른 빈에 주입되거나 반환 됩니다.
- 싱글턴 빈은 stateless한 빈에 사용하고, 프로토타입 빈은 stateful한 빈에 사용하도록 합니다.
- 위 그림에서 주의할 점은 DAO는 프로토타입으로 설정되는 경우가 거의 없습니다. 위 그림은 이전 예시를 재사용하여 만든 예시입니다.
- 스프링은 프로토타입 빈의 lifecycle(생명주기)을 관리하지 않습니다.
- 컨테이너가 인스턴스화 하고, 설정하고, 생성한 다음 클라이언트에게 건내주고 해당 프로토타입 인스턴스에 대해 추가적인 기록(관리)을 하지 않습니다.
- Initialization life cycle callback 메서드는 스코프 구분 없이 호출 되지만, 프로토타입 빈의 경우 destruction lifecycle callback 은 호출되지 않습니다.
- 프로토타입 스코프 오브젝트의 뒷처리와 해당 오브젝트가 지니고 있던 자원들의 정리는 클라이언트 코드에서 처리해야 합니다.
- 만약 스프링 컨테이너가 이런 뒷처리를 하도록 설정하고자 한다면, 커스텀한 bean post-processor를 사용할 수 있습니다.
- bean post-processor는 정리되어야 할 빈들의 참조를 가지고 있습니다.
- 프로토타입 빈과 관련된 스프링 컨테이너의 역할은 자바의 new 연산자를 대체하는것에 있습니다. 이 시점을 넘어간(빈의 생성 시점) 이후의 생명주기 관리는 온전히 클라이언트의 몫입니다.
- 스프링 컨테이너의 빈 생명주기를 더 보고 싶으시면 Lifecycle Callbacks를 참고하세요.
Singleton Beans with Prototype-bean Dependencies - 프로토타입 빈에 의존성을 가지는 싱글턴 빈
- 만약 프로토타입 빈에 의존성을 가지는 싱글턴 빈을 사용하게 된다면, 인스턴스화 시점에 의존성이 해결됩니다.
- 따라서, 프로토타입 빈을 싱글턴 빈에 의존성 주입하는 경우, 새로운 프로토타입 빈이 인스턴스화 되고 해당 싱글턴 빈에 주입되게 됩니다.
- 이 프로토타입 인스턴스는 해당 싱글턴 빈에 제공되는 유일한 인스턴스입니다.
- 싱글턴 빈은 스프링 컨테이너에 의해 인스턴스화 되며 단 한 번 생성되고 의존성 주입도 단 한 번 발생하기 때문에, 런타임에 반복적으로 새로운 프로토타입 빈을 싱글턴 빈에 의존성 주입하는 것은 불가능합니다.
- 만약 런타임에 새로운 프로토타입 빈의 인스턴스가 필요로 하는 경우에는 Method Injection을 참고하세요
Request, Session, Application, and WebSocket Scopes
- request, session, application, websocket 스코프는 web-aware 스프링 ApplicationContext 구현체를 사용해야만 사용할 수 있습니다.(XmlWebApplicationContext등)
- 만약 ClassPathXmlApplicationContext등을 사용하면 IllegalStateException을 발생시키며 알수 없는 빈 스코프는 사용할 수 없다 알려줍니다.
Request scope
@RequestScope
@Component
public class LoginAction {
// ...
}
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
- 스프링 컨테이너는 HTTP request가 발생할 때마다 loginAction 빈 정의를 사용해 LoginAction 빈을 생성합니다.
- 해당 객체의 내부 상태는 얼마든지 변경되어도 동일한 loginAction 빈정의로 생성된 다른 인스턴스에는 영향을 미치지 않는데, 이는 HTTP request가 발생할 때마다 새로운 인스턴스를 만들어내고 내부 상태의 변화는 서로 알 수 없기 때문입니다.
- 리퀘스트 스코프 빈은 리퀘스트에 특정됩니다.
- 리퀘스트가 처리 완료 되면 해당 리퀘스트의 빈은 버려집니다.
Session Scope
@SessionScope
@Component
public class UserPreferences {
// ...
}
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
- 스프링 컨테이너는 userPreferences 빈 정의를 사용하여 하나의 HTTP session의 수명에 대한 하나의 새로운 UserPreferences 빈 인스턴스를 생성합니다.
- request 스코프 빈과 비슷하게 이들 역시 내부 상태가 변경 되어도 동일한 빈 정의로 생성된 다른 빈 인스턴스에는 영향을 끼치지 않습니다.
- 세션이 끝나게 되면, 해당 세션의 빈도 버려집니다.
Application Scope
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
- 스프링 컨테이너는 appPreferences 빈 정의를 사용해 전체 웹 애플리케이션에 사용할 AppPreferences 빈 객체를 생성합니다.
- appPreferences 빈은 ServletContext레벨의 범위를 가지고, ServletContext 애트리뷰트에 저장됩니다.
- 어떻게 보면 싱글턴 빈과 비슷하다 느껴지지만 둘의 주요한 차이점은 다음과 같습니다.
- 스프링 애플리케이션 스코프 빈은 ServletContext당 하나의 싱글턴 빈을 생성하고 스프링 싱글턴 스코프 빈은 ApplicationContext(웹 애플리케이션에는 하나 이상의 ApplicationContext가 있을 수 있습니다.)당 하나의 싱글턴 빈을 생성합니다.
- 스프링 애플리케이션 스포크 빈은 ServletContext 애트리뷰트로 표시되고 외부로 노출됩니다.
WebSocket Scope
- 웹 소켓의 수명과 연관된 스코프입니다. 자세한 내용은 WebSocket scope 를 참조하세요.
Scoped Beans as Dependencies
- 스프링 IoC 컨테이너는 빈의 인스턴스화 외에도 의존성을 연결하는 일까지 관리합니다.
- 만약 HTTP request 스코프의 빈을 request 스코프 빈의 생명주기보다 긴 빈에 주입하고자 하는 경우 우리는 AOP 프록시를 대신 주입해줄 수 있습니다.
- 실제 스코프 오브젝트와 동일한 공용 인터페이스를 노출하는 프록시 객체를 주입해주는 방식입니다.
- 이 프록시 객체는 관련 스코프에 있는(예를 들어 HTTP request) 실제 타겟 객체를 검색하고 메서드 호출을 실제 객체에 위임할 수 있습니다.
- 실제 스코프 오브젝트와 동일한 공용 인터페이스를 노출하는 프록시 객체를 주입해주는 방식입니다.
싱글톤 스코프로 설정된 두 빈들에는 <aop:scoped-proxy/>를 사용할 수 있습니다. 이 참조는 직렬화 가능한 중간 프록시를 거치므로 역직렬화시에 타겟 싱글턴 빈을 다시 가져올 수 있습니다.
프로토타입 스코프의 빈에 <aop:scoped-proxy/>를 선언하게 되면 공유 프록시에서의 모든 메서드 호출이 새로운 인스턴스를 생성하게 하고, 새로 생성된 인스턴스에 호출을 전달합니다.
scoped proxies(범위 지정된 프록시)는 생명주기가 짧은 스코프의 빈에 lifecycle-safe한 방식으로 접근할 수 있는 유일한 방법이 아닙니다.
주입포인트(생성자 인수, 수정자 인수 혹은 오토와이어된 필드)를 ObjectFactory<MyTargetBean>으로 선언하여 필요할때마다 getObject() 호출을 통해 인스턴스를 계속 유지하거나 따로 저장할 필요 없이 현재 인스턴스를 얻어올 수 있도록 할 수 있습니다.
추가적으로 ObjectProvider<MyTargetBean>을 선언하면 getIfAvailable이나 getIfUnique같은 추가적인 변형메서드를 사용할 수 있습니다.
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">
<!-- HTTP session 스코프의 빈 설정. 프록시 객체를 노출하도록 설정 -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/> ⑴
</bean>
<!-- 위에서 설정된 빈의 프록시가 주입되는 싱글턴 스코프 빈 -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- 프록시 userPreferences 빈의 참조 정보 -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
- ⑴ 로 표시된 라인이 Proxy를 정의하는 부분입니다.
- 이런 프록시 객체를 생성하기 위해서는 프록시 객체를 생성할 빈 정의에 <aop:scoped-proxy/>를 사용해 줍니다.
- 다음 싱글턴 빈 정의 예시를 보고, 앞서 말한 스코프들(request, session 혹은 custom-scope level)을 정의하기 위해 무엇이 필요한지 비교해봅시다.
- 다음 예시에서 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 session 스코프의 userPreference 빈을 참조하도록 되어 있습니다.
- 여기서 주목할 부분은 userManager 빈은 싱글턴이라는 점인데, 싱글턴 빈은 컨테이너당 단 한 번 인스턴스화 되고 이 때 의존성 주입 역시 단 한 번 발생하게 됩니다.
- 즉, userManager 빈은 자신이 인스턴스화 된 시점에 주입 받은 userPreference 객체만으로 동작 하게 된다는 뜻입니다.
- 이런 동작 방식은 자신보다 스코프(생명주기)가 짧은 빈을 주입 받았을때에 원하는 방식이 아닙니다.
- 이보다는, 싱글턴 userManager 객체와, userManager가 특정 HTTP session마다의 userPreferences 객체를 사용하는 동작 방식이 필요합니다.
- 따라서, 컨테이너는 scoping mechanism(HTTP request, session등)에서 실제 UserPreferences 객체를 가져올 수 있도록 UserPreferences 클래스(이상적으로는 UserPreferences 인스턴스의 객체)와 정확히 동일한 공용 인터페이스를 노출하는 객체를 생성합니다.
- 그리고 컨테이너는 이 프록시 객체를 userManager빈에 주입하는데, userManager빈은 해당 객체가 프록시인지 알지 못합니다.
- 위 예시에서 UserManager인스턴스가 의존성 주입된 UserPreferences 객체의 메소드를 호출하게 되면, 실제로는 프록시 객체에 메서드를 호출하게 됩니다.
- 메서드가 호출 되면 프록시는 HTTP session에서 실제 UserPreferences 객체를 가지고 와 해당 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
- 기본적으로 스프링 컨테이너가 <aop:scoped-proxy/>설정된 빈의 프록시를 생성할 때, CGLIB 기반의 클래스 프록시가 생성됩니다.
CGLIB 프록시들은 private 메서드들을 인터셉트 하지 않습니다. CGLIB 프록시에서 private 메서드를 호출해도 실제 타겟 오브젝트에 메서드 호출을 위임하지 않습니다.
- CGLIB 방식을 원하지 않고 스프링 컨테이너에게 기본 JDK 인터페이스 기반 프록시를 생성하게 하고자 한다면 <aop:scoped-proxy/>에서 proxy-target-class 를 false 값으로 주어 설정할 수 있습니다.
- 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>
- class-based proxying과 interface-based proxying 관련 정보는 Proxying Mechanisms 를 참고하세요.
Injecting Request/Session References Directly
- factory scope(BeanFactory혹은 Application context를 통해 만들어진 빈들의 scope)의 대안으로 스프링의 WebApplicationContext는 HttpServletRequest, HttpServletResponse, HttpSession, WebRequest, 그리고 JSF가 있는 경우 FacesContext와 ExternalContext를 타입 기반 오토와이어링을 통해 스프링에서 관리하는 빈들에 주입할 수 있습니다.
- 이경우 스프링은 해당 객체의 프록시를 주입해주게 됩니다.
Custom Scopes
- 빈 스코프 메커니즘은 확장이 가능합니다. 직접 스코프를 정의하거나, 이미 존재하는 스코프를 재정의 할 수 있습니다.
- 주의할 점은 이미 존재하는 스코프를 재정의 하는 것은 bad practice이며 내장된 singleton과 prototype 스코프는 재정의 할 수 없습니다.
Creating a Custom Scope
- 커스텀 스코프를 스프링 컨테이너에 통합하기 위해서는 org.springframework.beans.factory.config.Scope 인터페이스를 구현해야 합니다.
- 커스텀 스코프를 만들기 위해 아이디어 혹은 예시를 찾고 싶다면, 스프링 프레임워크에서 제공하는 Scope 구현체들 혹은 Scope javadoc을 참고하세요.
- Scope 인터페이스는 어떤 스코프에서 객체를 가져오거나, 스포크에서 제거 혹은 파괴되도록 하는 4가지의 메서드를 가지고 있습니다.
- 예를 들어 session 스코프 구현체는 session 스코프 빈을 반환하는데, 만약 존재하지 않는 경우 메서드는 추후 참조를 위해 세션에 해당 빈의 새로운 인스턴스를 바운드 시킨 후 해당 인스턴스를 반환합니다.
Object get(String name, ObjectFactory<?> objectFactory)
- 또 다른 예시로 다음 메서드는 밑바탕되는 스코프에서 객체를 제거합니다. 제거 된 객체를 반환할 수도 있지만 지정된 이름의 객체가 없는 경우 null을 반환하도록 할 수 있습니다.
Object remove(String name)
- 다음 메서드는 스코프가 파괴(종료)되거나 스코프 안에 있는 특정 객체가 파괴되는 경우 실행되는 callback을 등록합니다.
void registerDestructionCallback(String name, Runnable destructionCallback)
- destruction callbacks 관련 자세한 내용은 javadoc 혹은 Spring scope implementation을 참고하세요.
- 다음 메서드는 밑바탕되는 스코프의 conversation identifier는 반환합니다.
- conversation이란 둘 이상의 participants들 사이에서 발생하는 지속적인 대화형 통신을 뜻합니다. requests, messages 혹은 이벤트등의 교환으로 통신을 하게 됩니다.- ChatGPT
String getConversationId()
- 이 identifier는 각 스코프마다 다릅니다. session 스코프 구현체라면, 이 identifier는 session identifier가 될 수 있습니다.
Using a Custom Scope
- 하나 이상의 커스텀 Scope 구현체를 만들고 테스트 했다면, 스프링 컨테이너에게 해당 스코프들을 알려줘야 합니다.
- 다음 메서드는 스프링 컨테이너로 새로운 Scope 구현체를 등록하는 메서드입니다.
void registerScope(String scopeName, Scope scope);
- 이 메서드는 ConfigurableBeanFactory에 선언되어 있으며, 스프링을 통해 제공되는 대부분의 ApplicationContext 구현체의 BeanFactory property를 통해 사용할 수 있습니다.
- registerScope의 첫번째 인수는 스코프의 유니크한 이름을 넣습니다.
- 두 번째 인수는 등록해서 사용하고자 하는 커스텀 스코프의 실제 인스턴스를 넣습니다.
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
- 그 다음, 생성한 커스텀 스코프의 규칙을 준수하는 빈 정의를 작성합니다.
<bean id="..." class="..." scope="thread">
- 사용자 정의 Scope 구현을 통해 Scope를 프로그래밍적으로 등록하는 것에 제한되지 않고, CustomScopeConfigurer 클래스를 사용하여 Scope 등록을 선언적으로 수행할 수도 있습니다.
<?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>
<aop:scoped-proxy/>를 <bean> 으로 감싸는 경우, 팩토리 빈 자체가 스코프를 갖게 되며, getObject()에서 반환되는 객체는 스코프가 적용되지 않습니다.
728x90
'Spring' 카테고리의 다른 글
면접관 : 스프링부트 auto-configuration의 동작방식을 설명해주세요. - 1 (0) | 2024.02.13 |
---|---|
확실히 스프링 시큐리티에 컨트리뷰트를 하고 나서 내 인생이 달라졌다. (0) | 2024.02.02 |
Lazy-initialized Beans (0) | 2024.01.24 |
Dependency Injection - 스프링 공식문서가 말하는 의존성 주입 (0) | 2024.01.23 |
Bean Overview - 스프링 공식문서가 말하는 빈 (0) | 2024.01.22 |