Spring-Bean作用域scope詳解

概述

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繼承結構

image

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");
            }
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章