面試題:原型Bean在一個執行緒多次獲取是否一樣?
技術標籤:javaspring多執行緒spring bootspring cloud
結論
對於原型Bean,IoC容器並沒有進行快取,所以每次呼叫getBean方法都是重新建立Bean,與執行緒無關。
程式碼測試
編寫一個類,新增上@Scope註解,然後指定其value為prototype。建立註解驅動應用上下文,呼叫register方法來註冊編寫的類的class,通過應用上下文的getBean(Class)方法來獲取應用上下文建立好的Bean例項。
package com.xxx.fame.prototype;
import org.springframework.beans.factory. config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
@Scope(ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBeanDemo {
public static void main(String[ ] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(PrototypeBeanDemo.class);
context.refresh();
PrototypeBeanDemo prototypeBeanDemo = context.getBean(PrototypeBeanDemo.class);
PrototypeBeanDemo prototypeBeanDemo2 = context.getBean(PrototypeBeanDemo.class);
System.out.println("第一次獲取到的 PrototypeBeanDemo 例項為:" + prototypeBeanDemo);
System.out.println("第二次獲取到的 PrototypeBeanDemo 例項為:" + prototypeBeanDemo2);
}
}
執行結果:
可以看到每次通過getBean(Class)方法獲取到的例項都是不一樣的。
之所以會出現這種問題,是因為IoC容器的doCreateBean方法所決定的。doCreateBean方法由Abstr-actBeanFactory類實現,在該方法中,首先根據beanName呼叫getSingleton方法(底層是去IoC容器中的一級快取、二級快取、三級快取查詢),因為原型Bean是不會被快取的,所以這裡獲取到的為空。
接下來根據beanName去獲取BeanDefinition資料,然後根據獲取到的BeanDefinition的isSingleton方法來判斷是否是單例的,由於我們指定Scope為原型(Prototype),這裡判斷失敗,接下來呼叫BeanD-efinition的isPrototype方法來判斷是否是一個原型Bean,判斷成立。
首先呼叫beforePrototypeCreation方法來標記該beanName正在建立(因為Bean的建立是一個很耗時的操作,所以需要提前進行標記),接下來就呼叫createBean方法真正地進行Bean建立,在finally塊中。看上去建立原型Bean和建立單例Bean沒什麼差別,最終都是呼叫createBean方法,問題就出在getSingleton方法上。
// AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 只保留和本次分析相關程式碼,省略其它程式碼...
} else {
// 只保留和本次分析相關程式碼,省略其它程式碼...
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// 只保留和本次分析相關程式碼,省略其它程式碼...
// 建立單例Bean
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()) {
// 建立原型Bean
Object prototypeInstance = null;
try {
// 標記指定beanName正在建立
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
} finally {
// 移除在beforePrototypeCreation方法中對beanName的標記
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// 只保留和本次分析相關程式碼,省略其它程式碼...
return (T) bean;
}
getSingleton方法由DefaultSingletonBeanRegistry類定義並實現,在該實現中,首先對一級快取-sin-gletonObject進行加鎖,然後再次根據beanName從一級快取中獲取(典型的Double Check),如果獲取到的結果還是等於null,呼叫beforeSingletonCreation方法來標記該beanName正在進行建立,在try程式碼塊中呼叫傳入的ObjectFactory例項的getObject方法,由於使用的是Lambda表示式,所以實際呼叫的還是createBean方法。
注意接下來的將newSingleton標誌位設定為true,這點非常重要,因為在方法的最後就是根據判斷該標誌位是否為true,來決定要不要呼叫addSingleton方法。
// DefaultSingletonBeanRegistry#getSingleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
// Double Check,雙重檢查
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
// 呼叫 beforeSingletonCreation 方法標記該beanName 正在建立
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 呼叫傳入的ObjectFactory例項的getObject方法,這裡使用的Lambda表示式,實際呼叫
singletonObject = singletonFactory.getObject();
// 注意這裡將newSingleton標誌位設定為true,這點非常重要,因為IoC容器就是根據該
// 標誌位來決定是否將bean新增到一級快取中。
newSingleton = true;
} catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
} catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
} finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
// 如果newSingleton標誌位為true,則呼叫addSingleton方法
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
addSinglton方法同樣由DefaultSingletonBeanRegistry定義並實現,該實現非常簡單,就是將建立好的bean例項儲存到singletonObjects中,並從singletonFactories(三級快取)以及earlySingletonObject-s(二級快取)中移除,最後在registeredSingletons集合也儲存一份beanName。
// DefaultSingletonBeanRegistry#addSingleton
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
總結
在一個執行緒內多次獲取原型Bean,每次返回的Bean例項都是不同的,之所以這樣是因為對於原型Bean,IoC容器是不會進行儲存的,和執行緒沒有任何關係。
這樣的設計也是合理的,因為每次請求原型Bean時IoC容器都需要建立,如果還保留其引用,那麼因為強引用而導致垃圾回收器無法進行GC(可達性分析演算法),從而導致記憶體洩漏,另外既然每次請求都需要建立,IoC容器儲存物件引用的意義又何在呢?