1. 程式人生 > 其它 >面試題:原型Bean在一個執行緒多次獲取是否一樣?

面試題:原型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容器儲存物件引用的意義又何在呢?