意義
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