天天看點

bean的作用域解析

作者:JAVA資料庫

意義

1.在Spring中,Bean的作用域可以通過scope屬性來指定。

2.指定作用域的目的是 存儲在此類單例bean的高速緩存中,并且對該命名bean的所有後續請求和引用都傳回該高速緩存的對象 。(本身的理念就是以空間換時間的思維,建立步驟繁雜,而且頻繁用到,我就存起來,下次用的時候就不用了建立了)

3.了解了目的之後,自然也就有了多種類型,大多數會使用singleton,當然也會有希望每次用到的就是新産生的故而出現prototype類型,還有就是某些範圍經常用到,另一些範圍不經常用到的,衍生了request和session的範圍性質的單例

類型與範圍

常見的有:

1) singleton :代表單例的,也是 預設值 (singleton存儲在三級緩存内,本質上是容器applicationcontext裡面的三級緩存)

2) prototype :代表多例的(prototype不會對bean進行存儲,而是在每次需要的時候進行建立)

3)request:代表範圍性質的單例(request存儲在對應的請求建構的請求對象裡面setAttribute)

4)session:代表範圍性質的單例(session存儲在對應的請求建構的請求對象裡面setAttribute)

5)application:application則是作用域整個應用裡面多個applicationcontext共享

6)包括自定義作用域

代碼展示

// mbd 指的是前面部分的 final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            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);
}

else {
    String scopeName = mbd.getScope();
    // 這一步擷取的就是存儲單例的緩存,針對不同類型擷取不同的緩存塊【如request對應的RequestScope,session對應的SessionScope】
    final Scope scope = this.scopes.get(scopeName);
    if (scope == null) {
        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
    }
    try {
        //類似于getSingleton的方式,在緩存中拿不到才會走工廠方法擷取
        Object scopedInstance = scope.get(beanName, () -> {
            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);
    }
}
           

代碼分析

對于Prototype部分的分析

1.先是涉及到檢測循環依賴部分的

beforePrototypeCreation(beanName); //記錄循環依賴,針對還沒有建立完成的Bean進行記錄

afterPrototypeCreation(beanName); //銷毀記錄,已建立完了就必須銷毀,不然A依賴于B,B都建立完了,你還覺得别人還沒建立

2.涉及建立Bean部分的

了解過源碼的都知道,在建立過程中,如果bean執行個體化但是未初始化會有一個對外暴露的方式,就是存儲于單例池中

故對于多例情況,bean是不做緩存的

對于Singleton部分的分析

對于單例的bean有它自己的處理邏輯, getSingleton方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    //加鎖是保證單例建立的不沖突
    synchronized (this.singletonObjects) {
        //嘗試從單例池中擷取
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            //記錄循環依賴,針對還沒有建立完成的Bean進行記錄
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                //從工廠方法中,建立bean對象
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            catch (IllegalStateException ex) {}
            catch (BeanCreationException ex) {}
            finally {
                 //銷毀記錄,已建立完了就必須銷毀
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                //建立完了要添加進入單例池
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}
           

對于其餘部分的分析(包括request,session等和自定義都是走這部分的邏輯)

針對request,session等,代碼 scope.get 這部分深入進去其實是通用方法(也是模闆設計模式), AbstractRequestAttributesScope類#get方法 :

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
    RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
    Object scopedObject = attributes.getAttribute(name, getScope());
    if (scopedObject == null) {
        scopedObject = objectFactory.getObject();
        attributes.setAttribute(name, scopedObject, getScope());
        // Retrieve object again, registering it for implicit session attribute updates.
        // As a bonus, we also allow for potential decoration at the getAttribute level.
        Object retrievedObject = attributes.getAttribute(name, getScope());
        if (retrievedObject != null) {
            // Only proceed with retrieved object if still present (the expected case).
            // If it disappeared concurrently, we return our locally created instance.
            scopedObject = retrievedObject;
        }
    }
    return scopedObject;
}
           

這塊便是針對緩存的擷取,通用了解為 attributes.getAttribute(name, getScope()); 等同于session.getAttribute( beanName )或 request .getAttribute( beanName )

工廠方法( Lambda表達式部分 )針對的便是緩存沒有時候的建立邏輯

分析彙總

1. 對于作用域,本質上是 存儲在此類單例bean的高速緩存中,并且對該命名bean的所有後續請求和引用都傳回該高速緩存的對象 ,便是為了達到以空間換時間的優化方式。

2. 對于建立Bean,都要進行循環依賴的預防。

AbstractRequestAttributesScope