概述
Bean的作用域:
什麼是作用域呢?即“scope”,在面向對象程序設計中一般指對象或變量之間的可見範圍。而在Spring容器中是指其創建的Bean對象相對於其他Bean對象的請求可見範圍。
Spring支持以下作用域:
- 基本作用域:singleton、prototype
- web作用域 :request、session、global session
- 自定義作用域
詳析
基本作用域
Singleton作用域
當一個bean的作用域爲singleton, 那麼Spring IoC容器中只會存在一個共享的bean實例,每次請求該bean都會返回bean的同一實例。
換言之,當把一個bean定義設置爲singlton作用域時,Spring IoC容器只會創建該bean定義的唯一實例。這個單一實例會被存儲到單例緩存(singleton cache)中,並且所有針對該bean的後續請求和引用都將返回被緩存的對象實例。
Prototype
Prototype作用域的bean會導致在每次請求該id的Bean時都會創建一個新的bean實例,然後返回給程序。這種情況下,spring容器僅僅使用new關鍵字創建Bean實例,一旦創建成功,容器不再維護Bean實例的狀態。
demo
xml配置
<bean class="com.lili.scope.service.SingletonService" scope="singleton" id="singletonService" />
<bean class="com.lili.scope.service.PrototypeScopeService" scope="prototype" id="prototypeScopeService"/>
@RestController
@RequestMapping("/service")
public class ScopeController {
@RequestMapping(value="/scope",method = RequestMethod.GET)
public void singletonReuest(){
System.out.println("singletonService===="+RuntimeContext.getBean("singletonService"));
System.out.println("prototypeScopeService====1====="+RuntimeContext.getBean("prototypeScopeService"));
System.out.println("prototypeScopeService====2====="+RuntimeContext.getBean("prototypeScopeService"));
}
}
兩次請求結果:
/*********第一次請求*****************/
singletonService====com.lili.scope.service.SingletonService@5c34c8da
prototypeScopeService====1=====com.lili.scope.service.PrototypeScopeService@4a055767
prototypeScopeService====2=====com.lili.scope.service.PrototypeScopeService@44c2b057
/*********第二次請求*****************/
singletonService====com.lili.scope.service.SingletonService@5c34c8da
prototypeScopeService====1=====com.lili.scope.service.PrototypeScopeService@3e571966
prototypeScopeService====2=====com.lili.scope.service.PrototypeScopeService@68655866
我們可以從結果中發現,兩次請求中singletonService的對象是同一個,同一個請求中的兩次調用prototypeScopeService都不是同一個對象。
web作用域
request、session作用域只在web應用中才有效,並且必須在web應用中增加相應配置纔會生效。爲了讓request、session作用域生效,必須把HTTP請求對象綁定到爲該請求提供服務的線程上,使得具有request和session作用域的Bean實例能夠在後面的調用中被訪問。
Request作用域
考慮下面bean定義:
針對每次HTTP請求,Spring容器會創建一個全新的bean實例,且該bean實例僅在當前HTTP request內有效,因此可以根據需要放心的更改所建實例的內部狀態,而其他請求中獲得的 bean實例不會看到這些特定於某個請求的狀態變化。當處理請求結束,request作用域的bean實例將被銷燬。
Session作用域
考慮下面bean定義:
Session作用域與request作用域一樣,區別在於:request作用域的Bean對於每次HTTP請求有效,而session作用域的Bean則對應每次HTTP Session有效。
gloable session作用域
global session作用域類似於標準的HTTP Session作用域,不過它僅僅在基於portlet的web應用中才有意義。Portlet規範定義了全局Session的概念,它被所有構成某個portlet web應用的各種不同的portlet所共享。在global session作用域中定義的bean被限定於全局portlet Session的生命週期範圍內。
demo
xml配置
<bean class="com.lili.scope.service.RequestScopeService" scope="request" id="requestScopeService" />
<bean class="com.lili.scope.service.SessionScopeService" scope="session" id="sessionScopeService"/>
測試代碼
@RequestMapping(value="/requestScope",method = RequestMethod.GET)
public void requestScope(){
System.out.println("requestScopeService===="+RuntimeContext.getBean("requestScopeService"));
System.out.println("sessionScopeService===="+RuntimeContext.getBean("sessionScopeService"));
}
請求結果:
/*********第一次請求*****************/
requestScopeService====com.lili.scope.service.RequestScopeService@2e945244
sessionScopeService====com.lili.scope.service.SessionScopeService@4fbb381e
/*********第二次請求*****************/
requestScopeService====com.lili.scope.service.RequestScopeService@68f187e7
sessionScopeService====com.lili.scope.service.SessionScopeService@4fbb381e
/*********第三次請求*****************/
requestScopeService====com.lili.scope.service.RequestScopeService@3682c6a7
sessionScopeService====com.lili.scope.service.SessionScopeService@4fbb381e
第一、第二次請求在同一個瀏覽器,也就是同一個session,第三次請求在新的瀏覽器請求:不同瀏覽器(不同session)返回的 Bean 實例不同,而同一個瀏覽器(同一會話)多次發起請求返回的是同一個 Bean 實例。
自定義作用域
scope接口
public interface Scope {
// 從作用域中獲取Bean,先暴露ObjectFactory對象,解決了循環依賴的問題,
// bean的實際創建是ObjectFactory#getObject()
Object get(String name, ObjectFactory<?> objectFactory);
// 從作用域中移除 Bean
Object remove(String name);
// 用於註冊銷燬回調,如果想要銷燬相應的對象則由Spring容器註冊相應的銷燬回調,而由自定義作用域選擇是不是要銷燬相應的對象;
void registerDestructionCallback(String name, Runnable callback);
//用於解析相應的上下文數據,比如request作用域將返回request中的屬性。
Object resolveContextualObject(String key);
// 作用域的會話標識,比如session作用域將是sessionId。
String getConversationId();
}
示例demo
xml配置
<!-- CustomScopeConfigurer 的 scopes 屬性註冊自定義作用域實現 -->
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread" >
<bean class="com.lili.scope.CustomerScope"/>
</entry>
</map>
</property>
</bean>
<bean class="com.lili.scope.service.CustomerScopeService" scope="thread" id="customerScopeService" />
自定義的scope類
/**
* @Autor: lili
* @Date: 2018/3/30-14:56
* @Description: 自定義scope,作用範圍同一個Thread
*/
public class CustomerScope implements Scope{
private static final ThreadLocal<Map<String,Object>> THREAD_BEAN_MAP = new ThreadLocal<Map<String, Object>>(){
//初始化自定義上下文數據
protected Map<String,Object> initialValue(){
return new HashMap<>();
}
};
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String,Object> beanMap = THREAD_BEAN_MAP.get();
if(!beanMap.containsKey(name)){
beanMap.put(name,objectFactory.getObject());
}
return beanMap.get(name);
}
@Override
public Object remove(String name) {
Map<String,Object> beanMap = THREAD_BEAN_MAP.get();
return beanMap.remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}
調用代碼:
@RequestMapping(value="/threadScope",method = RequestMethod.GET)
public void Scope() throws InterruptedException {
System.out.println("customerScopeService===="+RuntimeContext.getBean("customerScopeService"));
// 新建線程
new Thread(){ public void run() {
System.out.println("customerScopeService in new Thread ===="+RuntimeContext.getBean("customerScopeService"));
}}.start();
Thread.sleep(1000);
System.out.println("customerScopeService===="+RuntimeContext.getBean("customerScopeService"));
}
請求結果:
customerScopeService====com.lili.scope.service.CustomerScopeService@1c6a2bcf
customerScopeService in new Thread ====com.lili.scope.service.CustomerScopeService@2948c3e6
customerScopeService in new Thread ====com.lili.scope.service.CustomerScopeService@2948c3e6
customerScopeService====com.lili.scope.service.CustomerScopeService@1c6a2bcf
我們可以發現在同一個Thread中的customerScopeService對象是同一個。
scope源碼分析
scope#get()方法的調用
在bean的實例化過程中會對bean的scope進行判斷,下面看一下AbstractBeanFactory#doGetBean()方法,這裏的代碼省略了很多,具體的初始化可以看一下《Spring Bean的初始化》。
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
//.......
}
else {
//.......
try {
//.....
// Create bean instance.
//單例,緩存
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
//原型 直接創建一個新的對象
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
//web或自定義的scope
else {
//取出相應的scope實例,如:RequestScope、SessionScope等
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
}
try {
//scope的get方法具體調用,下面可以具體分析不同的Scope
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; " +
"consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// .....省略
return (T) bean;
}
scope繼承結構
AbstractRequestAttributesScope
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// RequestContextHolder中通過ThreadLocal將RequestAttributes實例和當前線程綁定
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
//根據scope的不同,進行不同的操作,具體方法在ServletRequestAttributes中
Object scopedObject = attributes.getAttribute(name, getScope());
// 從當前線程綁定的RequestAttributes中獲取對象
// 如果已經實例化過了,則不再實例化
// 否則進入Spring#createBean()獲取對象
if (scopedObject == null) {
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, getScope());
}
return scopedObject;
}
ServletRequestAttributes#getAttribute
@Override
public Object getAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attribute - request is not active anymore!");
}
return this.request.getAttribute(name);
}
else {
HttpSession session = getSession(false);
if (session != null) {
try {
Object value = session.getAttribute(name);
if (value != null) {
this.sessionAttributesToUpdate.put(name, value);
}
return value;
}
catch (IllegalStateException ex) {
// Session invalidated - shouldn't usually happen.
}
}
return null;
}
}
RequestContextHolder
public abstract class RequestContextHolder {
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
/**
* Reset the RequestAttributes for the current thread.
*/
public static void resetRequestAttributes() {
requestAttributesHolder.remove();
inheritableRequestAttributesHolder.remove();
}
/**
* Bind the given RequestAttributes to the current thread,
*/
public static void setRequestAttributes(RequestAttributes attributes) {
setRequestAttributes(attributes, false);
}
/**
* Bind the given RequestAttributes to the current thread.
*/
public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
}
else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
}
else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}
/**
* Return the RequestAttributes currently bound to the thread.
*/
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
/**
* Return the RequestAttributes currently bound to the thread.
*/
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new IllegalStateException(".....");
}
}
return attributes;
}
/**
* Inner class to avoid hard-coded JSF dependency.
*/
private static class FacesRequestAttributesFactory {
public static RequestAttributes getFacesRequestAttributes() {
FacesContext facesContext = FacesContext.getCurrentInstance();
return (facesContext != null ? new FacesRequestAttributes(facesContext) : null);
}
}
}
RequestScope與SessionScope
public class RequestScope extends AbstractRequestAttributesScope {
@Override
protected int getScope() {
return RequestAttributes.SCOPE_REQUEST;
}
/**
* There is no conversation id concept for a request, so this method
* returns {@code null}.
*/
@Override
public String getConversationId() {
return null;
}
}
public class SessionScope extends AbstractRequestAttributesScope {
private final int scope;
public SessionScope(boolean globalSession) {
this.scope = (globalSession ? RequestAttributes.SCOPE_GLOBAL_SESSION : RequestAttributes.SCOPE_SESSION);
}
@Override
protected int getScope() {
return this.scope;
}
@Override
public String getConversationId() {
return RequestContextHolder.currentRequestAttributes().getSessionId();
}
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
synchronized (mutex) {
return super.get(name, objectFactory);
}
}
@Override
public Object remove(String name) {
Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
synchronized (mutex) {
return super.remove(name);
}
}
}
SessionScope和RequestScope都是AbstractRequestAttributesScope的子類,獲取對象的方式都一樣,不同的是生命週期以及SessionScope#get方法加了鎖synchronized。
scope實例的註冊
AbstractApplicationContext#refresh()
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// spring 中RequestScope SessionScope等Scope在這裏完成註冊
postProcessBeanFactory(beanFactory);
//自定義的Scope的註冊是在這裏完成註冊的
invokeBeanFactoryPostProcessors(beanFactory);
//.....省略
}
catch (BeansException ex) {
//........省略
}
finally {
//.....省略
}
}
}
AbstractRefreshableWebApplicationContext#postProcessBeanFactory
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
//
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}
WebApplicationContextUtils#registerWebApplicationScopes
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
if (sc != null) {
ServletContextScope appScope = new ServletContextScope(sc);
beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
sc.setAttribute(ServletContextScope.class.getName(), appScope);
}
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
if (jsfPresent) {
FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
}
}
自定義scope實例的註冊
CustomScopeConfigurer#postProcessBeanFactory
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.scopes != null) {
for (Map.Entry<String, Object> entry : this.scopes.entrySet()) {
String scopeKey = entry.getKey();
Object value = entry.getValue();
if (value instanceof Scope) {
beanFactory.registerScope(scopeKey, (Scope) value);
}
else if (value instanceof Class) {
Class<?> scopeClass = (Class<?>) value;
Assert.isAssignable(Scope.class, scopeClass);
beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
}
else if (value instanceof String) {
Class<?> scopeClass = ClassUtils.resolveClassName((String) value, this.beanClassLoader);
Assert.isAssignable(Scope.class, scopeClass);
beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
}
else {
throw new IllegalArgumentException("Mapped value [" + value + "] for scope key [" +
scopeKey + "] is not an instance of required type [" + Scope.class.getName() +
"] or a corresponding Class or String value indicating a Scope implementation");
}
}
}
}