javascript
Spring框架—SpringBean加载过程
原文作者:RunAlgorithm
原文地址:圖文并茂,揭秘 Spring 的 Bean 的加載過程
?
1. 概述
Spring 作為 Ioc 框架,實現了依賴注入,由一個中心化的 Bean 工廠來負責各個 Bean 的實例化和依賴管理。各個 Bean 可以不需要關心各自的復雜的創建過程,達到了很好的解耦效果。我們對 Spring 的工作流進行一個粗略的概括,主要為兩大環節:
- 解析:讀 xml 配置,掃描類文件,從配置或者注解中獲取 Bean 的定義信息,注冊一些擴展功能。
- 加載:通過解析完的定義信息獲取 Bean 實例。
?
我們假設所有的配置和擴展類都已經裝載到了 ApplicationContext 中,然后具體的分析一下 Bean 的加載流程。思考一個問題,拋開 Spring 框架的實現,假設我們手頭上已經有一套完整的 Bean Definition Map,然后指定一個 beanName 要進行實例化,需要關心什么?即使我們沒有 Spring 框架,也需要了解這兩方面的知識:
- 作用域:單例作用域或者原型作用域,單例的話需要全局實例化一次,原型每次創建都需要重新實例化。
- 依賴關系:一個 Bean 如果有依賴,我們需要初始化依賴,然后進行關聯。如果多個 Bean 之間存在著循環依賴,A 依賴 B,B 依賴 C,C 又依賴 A,需要解這種循環依賴問題。
Spring 進行了抽象和封裝,使得作用域和依賴關系的配置對開發者透明,我們只需要知道當初在配置里已經明確指定了它的生命周期和依賴了誰,至于是怎么實現的,依賴如何注入,托付給了 Spring 工廠來管理。Spring 只暴露了很簡單的接口給調用者,比如 getBean :
ApplicationContext context = new ClassPathXmlApplicationContext("hello.xml"); HelloBean helloBean = (HelloBean) context.getBean("hello"); helloBean.sayHello();那我們就從 getBean 方法作為入口,去理解 Spring 加載的流程是怎樣的,以及內部對創建信息、作用域、依賴關系等等的處理細節。
2. 總體流程
?
Bean 加載流程圖上面是跟蹤了 getBean 的調用鏈創建的流程圖,為了能夠很好地理解 Bean 加載流程,省略一些異常、日志和分支處理和一些特殊條件的判斷。從上面的流程圖中,可以看到一個 Bean 加載會經歷這么幾個階段(用綠色標記):
- 獲取 BeanName:對傳入的 name 進行解析,轉化為可以從 Map 中獲取到 BeanDefinition 的 bean name。
- 合并 Bean 定義:對父類的定義進行合并和覆蓋,如果父類還有父類,會進行遞歸合并,以獲取完整的 Bean 定義信息。
- 實例化:使用構造或者工廠方法創建 Bean 實例。
- 屬性填充:尋找并且注入依賴,依賴的 Bean 還會遞歸調用 getBean 方法獲取。
- 初始化:調用自定義的初始化方法。
- 獲取最終的 Bean:如果是 FactoryBean 需要調用 getObject 方法,如果需要類型轉換調用 TypeConverter 進行轉化。
整個流程最為復雜的是對循環依賴的解決方案,后續會進行重點分析。
3. 細節分析
3.1. 轉化 BeanName
而在我們解析完配置后創建的 Map,使用的是 beanName 作為 key。見 DefaultListableBeanFactory:
/** Map of bean definition objects, keyed by bean name */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);BeanFactory.getBean 中傳入的 name,有可能是這幾種情況:
- bean name:可以直接獲取到定義 BeanDefinition。
- alias name:別名,需要轉化。
- factorybean name:?帶 & 前綴,通過它獲取 BeanDefinition 的時候需要去除 & 前綴。
為了能夠獲取到正確的 BeanDefinition,需要先對 name 做一個轉換,得到 beanName。
name轉beanName?
?
見 AbstractBeanFactory.doGetBean:
protected <T> T doGetBean ... {...// 轉化工作 final String beanName = transformedBeanName(name);... }如果是 alias name,在解析階段,alias name 和 bean name 的映射關系被注冊到 SimpleAliasRegistry 中。從該注冊器中取到 beanName。見 SimpleAliasRegistry.canonicalName:
public String canonicalName(String name) {...resolvedName = this.aliasMap.get(canonicalName);... }如果是 factorybean name,表示這是個工廠 bean,有攜帶前綴修飾符 & 的,直接把前綴去掉。見 BeanFactoryUtils.transformedBeanName :
public static String transformedBeanName(String name) {Assert.notNull(name, "'name' must not be null");String beanName = name;while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());}return beanName; }3.2. 合并 RootBeanDefinition
我們從配置文件讀取到的 BeanDefinition 是 GenericBeanDefinition。它記錄了一些當前類聲明的屬性或構造參數,但是對于父類只用了一個 parentName 來記錄。
public class GenericBeanDefinition extends AbstractBeanDefinition {...private String parentName;... }接下來會發現一個問題,在后續實例化 Bean 的時候,使用的 BeanDefinition 是 RootBeanDefinition 類型而非 GenericBeanDefinition。這是為什么?答案很明顯,GenericBeanDefinition 在有繼承關系的情況下,定義的信息不足:
- 如果不存在繼承關系,GenericBeanDefinition 存儲的信息是完整的,可以直接轉化為 RootBeanDefinition。
- 如果存在繼承關系,GenericBeanDefinition 存儲的是 增量信息 而不是 全量信息。
為了能夠正確初始化對象,需要完整的信息才行。需要遞歸 合并父類的定義:
合并BeanDefinition?
見 AbstractBeanFactory.doGetBean :
protected <T> T doGetBean ... {...// 合并父類定義final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);...// 使用合并后的定義進行實例化bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);... }在判斷 parentName 存在的情況下,說明存在父類定義,啟動合并。如果父類還有父類怎么辦?遞歸調用,繼續合并。見AbstractBeanFactory.getMergedBeanDefinition 方法:
protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd, BeanDefinition containingBd)throws BeanDefinitionStoreException {...String parentBeanName = transformedBeanName(bd.getParentName());...// 遞歸調用,繼續合并父類定義pbd = getMergedBeanDefinition(parentBeanName);...// 使用合并后的完整定義,創建 RootBeanDefinitionmbd = new RootBeanDefinition(pbd);// 使用當前定義,對 RootBeanDefinition 進行覆蓋mbd.overrideFrom(bd);...return mbd;}每次合并完父類定義后,都會調用 RootBeanDefinition.overrideFrom 對父類的定義進行覆蓋,獲取到當前類能夠正確實例化的 全量信息。
3.3. 處理循環依賴
什么是循環依賴?舉個例子,這里有三個類 A、B、C,然后 A 關聯 B,B 關聯 C,C 又關聯 A,這就形成了一個循環依賴。如果是方法調用是不算循環依賴的,循環依賴必須要持有引用。
循環依賴?
循環依賴根據注入的時機分成兩種類型:
- 構造器循環依賴:依賴的對象是通過構造器傳入的,發生在實例化 Bean 的時候。這種依賴本質上是無法解決的。比如我們準調用 A 的構造器,發現依賴 B,于是去調用 B 的構造器進行實例化,發現又依賴 C,于是調用 C 的構造器去初始化,結果依賴 A,整個形成一個死結,導致 A 無法創建。
- 設值循環依賴:依賴的對象是通過 setter 方法傳入的,對象已經實例化,發生屬性填充和依賴注入的時候。Spring 框架只支持單例下的設值循環依賴。Spring 通過對還在創建過程中的單例,緩存并提前暴露該單例,使得其他實例可以引用該依賴。
3.3.1. 原型模式的循環依賴
Spring 不支持原型模式的任何循環依賴。檢測到循環依賴會直接拋出 BeanCurrentlyInCreationException 異常。使用了一個 ThreadLocal 變量 prototypesCurrentlyInCreation 來記錄當前線程正在創建中的 Bean 對象,見 AbtractBeanFactory#prototypesCurrentlyInCreation:
/** Names of beans that are currently in creation */ private final ThreadLocal<Object> prototypesCurrentlyInCreation =new NamedThreadLocal<Object>("Prototype beans currently in creation");在 Bean 創建前進行記錄,在 Bean 創建后刪除記錄。見 AbstractBeanFactory.doGetBean:
... 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); } ...見 AbtractBeanFactory.beforePrototypeCreation 的記錄操作:
protected void beforePrototypeCreation(String beanName) {Object curVal = this.prototypesCurrentlyInCreation.get();if (curVal == null) {this.prototypesCurrentlyInCreation.set(beanName);}else if (curVal instanceof String) {Set<String> beanNameSet = new HashSet<String>(2);beanNameSet.add((String) curVal);beanNameSet.add(beanName);this.prototypesCurrentlyInCreation.set(beanNameSet);}else {Set<String> beanNameSet = (Set<String>) curVal;beanNameSet.add(beanName);}}見 AbtractBeanFactory.beforePrototypeCreation 的刪除操作:
protected void afterPrototypeCreation(String beanName) {Object curVal = this.prototypesCurrentlyInCreation.get();if (curVal instanceof String) {this.prototypesCurrentlyInCreation.remove();}else if (curVal instanceof Set) {Set<String> beanNameSet = (Set<String>) curVal;beanNameSet.remove(beanName);if (beanNameSet.isEmpty()) {this.prototypesCurrentlyInCreation.remove();}}}為了節省內存空間,在單個元素時 prototypesCurrentlyInCreation 只記錄 String 對象,在多個依賴元素后改用 Set 集合。這里是 Spring 使用的一個節約內存的小技巧。了解了記錄的寫入和刪除過程好了,再來看看讀取以及判斷循環的方式。這里要分兩種情況討論。
- 構造函數循環依賴。
- 設置循環依賴。
這兩個地方的實現略有不同。如果是構造函數依賴的,比如 A 的構造函數依賴了 B,會有這樣的情況。實例化 A 的階段中,匹配到要使用的構造函數,發現構造函數有參數 B,會使用 BeanDefinitionValueResolver 來檢索 B 的實例。見 BeanDefinitionValueResolver.resolveReference:
private Object resolveReference(Object argName, RuntimeBeanReference ref) {...Object bean = this.beanFactory.getBean(refName);... }我們發現這里繼續調用 beanFactory.getBean 去加載 B。如果是設值循環依賴的的,比如我們這里不提供構造函數,并且使用了 @Autowire 的方式注解依賴(還有其他方式不舉例了):
public class A {@Autowiredprivate B b;... }加載過程中,找到無參數構造函數,不需要檢索構造參數的引用,實例化成功。接著執行下去,進入到屬性填充階段 AbtractBeanFactory.populateBean ,在這里會進行 B 的依賴注入。為了能夠獲取到 B 的實例化后的引用,最終會通過檢索類 DependencyDescriptor 中去把依賴讀取出來,見 DependencyDescriptor.resolveCandidate :
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)throws BeansException {return beanFactory.getBean(beanName, requiredType); }發現 beanFactory.getBean 方法又被調用到了。在這里,兩種循環依賴達成了同一。無論是構造函數的循環依賴還是設置循環依賴,在需要注入依賴的對象時,會繼續調用 beanFactory.getBean 去加載對象,形成一個遞歸操作。而每次調用 beanFactory.getBean 進行實例化前后,都使用了 prototypesCurrentlyInCreation 這個變量做記錄。按照這里的思路走,整體效果等同于 建立依賴對象的構造鏈。prototypesCurrentlyInCreation 中的值的變化如下:
原型模式的循環依賴?
調用判定的地方在 AbstractBeanFactory.doGetBean 中,所有對象的實例化均會從這里啟動。
// Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName); }判定的實現方法為 AbstractBeanFactory.isPrototypeCurrentlyInCreation :
protected boolean isPrototypeCurrentlyInCreation(String beanName) {Object curVal = this.prototypesCurrentlyInCreation.get();return (curVal != null &&(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName)))); }所以在原型模式下,構造函數循環依賴和設值循環依賴,本質上使用同一種方式檢測出來。Spring 無法解決,直接拋出 BeanCurrentlyInCreationException 異常。
3.3.2. 單例模式的構造循環依賴
Spring 也不支持單例模式的構造循環依賴。檢測到構造循環依賴也會拋出 BeanCurrentlyInCreationException 異常。和原型模式相似,單例模式也用了一個數據結構來記錄正在創建中的 beanName。見 DefaultSingletonBeanRegistry:
/** Names of beans that are currently in creation */ private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));會在創建前進行記錄,創建化后刪除記錄。見 DefaultSingletonBeanRegistry.getSingleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {...// 記錄正在加載中的 beanNamebeforeSingletonCreation(beanName);...// 通過 singletonFactory 創建 beansingletonObject = singletonFactory.getObject();...// 刪除正在加載中的 beanNameafterSingletonCreation(beanName);}記錄和判定的方式見 DefaultSingletonBeanRegistry.beforeSingletonCreation :
protected void beforeSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}}這里會嘗試往 singletonsCurrentlyInCreation 記錄當前實例化的 bean。我們知道 singletonsCurrentlyInCreation 的數據結構是 Set,是不允許重復元素的,所以一旦前面記錄了,這里的 add 操作將會返回失敗。比如加載 A 的單例,和原型模式類似,單例模式也會調用匹配到要使用的構造函數,發現構造函數有參數 B,然后使用 BeanDefinitionValueResolver 來檢索 B 的實例,根據上面的分析,繼續調用 beanFactory.getBean 方法。所以拿 A,B,C 的例子來舉例 singletonsCurrentlyInCreation 的變化,這里可以看到和原型模式的循環依賴判斷方式的算法是一樣:
單例模式的構造循環依賴?
- 加載 A。記錄 singletonsCurrentlyInCreation = [a],構造依賴 B,開始加載 B。
- 加載 B,記錄 singletonsCurrentlyInCreation = [a, b],構造依賴 C,開始加載 C。
- 加載 C,記錄 singletonsCurrentlyInCreation = [a, b, c],構造依賴 A,又開始加載 A。
- 加載 A,執行到 DefaultSingletonBeanRegistry.beforeSingletonCreation ,singletonsCurrentlyInCreation 中 a 已經存在了,檢測到構造循環依賴,直接拋出異常結束操作。
3.3.3. 單例模式的設值循環依賴
單例模式下,構造函數的循環依賴無法解決,但設值循環依賴是可以解決的。這里有一個重要的設計:提前暴露創建中的單例。我們理解一下為什么要這么做。還是拿上面的 A、B、C 的的設值依賴做分析,
- => 1. A 創建 -> A 構造完成,開始注入屬性,發現依賴 B,啟動 B 的實例化
- => 2. B 創建 -> B 構造完成,開始注入屬性,發現依賴 C,啟動 C 的實例化
- => 3. C 創建 -> C 構造完成,開始注入屬性,發現依賴 A
重點來了,在我們的階段 1中, A 已經構造完成,Bean 對象在堆中也分配好內存了,即使后續往 A 中填充屬性(比如填充依賴的 B 對象),也不會修改到 A 的引用地址。所以,這個時候是否可以 提前拿 A 實例的引用來先注入到 C ,去完成 C 的實例化,于是流程變成這樣。
- => 3. C 創建 -> C 構造完成,開始注入依賴,發現依賴 A,發現 A 已經構造完成,直接引用,完成 C 的實例化。
- => 4. C 完成實例化后,B 注入 C 也完成實例化,A 注入 B 也完成實例化。
這就是 Spring 解決單例模式設值循環依賴應用的技巧。流程圖為:
單例模式創建流程?
為了能夠實現單例的提前暴露。Spring 使用了三級緩存,見 DefaultSingletonBeanRegistry:
/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);/** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);/** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);這三個緩存的區別如下:
- singletonObjects,單例緩存,存儲已經實例化完成的單例。
- singletonFactories,生產單例的工廠的緩存,存儲工廠。
- earlySingletonObjects,提前暴露的單例緩存,這時候的單例剛剛創建完,但還會注入依賴。
從 getBean("a") 開始,添加的 SingletonFactory 具體實現如下:
protected Object doCreateBean ... {...addSingletonFactory(beanName, new ObjectFactory<Object>() {@Overridepublic Object getObject() throws BeansException {return getEarlyBeanReference(beanName, mbd, bean);}});... }可以看到如果使用該 SingletonFactory 獲取實例,使用的是 getEarlyBeanReference 方法,返回一個未初始化的引用。讀取緩存的地方見 DefaultSingletonBeanRegistry :
protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return (singletonObject != NULL_OBJECT ? singletonObject : null); }先嘗試從 singletonObjects 和 singletonFactory 讀取,沒有數據,然后嘗試 singletonFactories 讀取 singletonFactory,執行 getEarlyBeanReference 獲取到引用后,存儲到 earlySingletonObjects 中。這個 earlySingletonObjects 的好處是,如果此時又有其他地方嘗試獲取未初始化的單例,可以從 earlySingletonObjects 直接取出而不需要再調用 getEarlyBeanReference。從流程圖上看,實際上注入 C 的 A 實例,還在填充屬性階段,并沒有完全地初始化。等遞歸回溯回去,A 順利拿到依賴 B,才會真實地完成 A 的加載。
3.4. 創建實例
獲取到完整的 RootBeanDefintion 后,就可以拿這份定義信息來實例具體的 Bean。具體實例創建見 AbstractAutowireCapableBeanFactory.createBeanInstance ,返回 Bean 的包裝類 BeanWrapper,一共有三種策略:
- 使用工廠方法創建,instantiateUsingFactoryMethod 。
- 使用有參構造函數創建,autowireConstructor。
- 使用無參構造函數創建,instantiateBean。
使用工廠方法創建,會先使用 getBean 獲取工廠類,然后通過參數找到匹配的工廠方法,調用實例化方法實現實例化,具體見ConstructorResolver.instantiateUsingFactoryMethod :
public BeanWrapper instantiateUsingFactoryMethod ... (...String factoryBeanName = mbd.getFactoryBeanName();...factoryBean = this.beanFactory.getBean(factoryBeanName);...// 匹配正確的工廠方法...beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(...);...bw.setBeanInstance(beanInstance);return bw; }使用有參構造函數創建,整個過程比較復雜,涉及到參數和構造器的匹配。為了找到匹配的構造器,Spring 花了大量的工作,見 ConstructorResolver.autowireConstructor :
public BeanWrapper autowireConstructor ... {...Constructor<?> constructorToUse = null;...// 匹配構造函數的過程...beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(...);...bw.setBeanInstance(beanInstance);return bw; }使用無參構造函數創建是最簡單的方式,見 AbstractAutowireCapableBeanFactory.instantiateBean:
protected BeanWrapper instantiateBean ... {...beanInstance = getInstantiationStrategy().instantiate(...);...BeanWrapper bw = new BeanWrapperImpl(beanInstance);initBeanWrapper(bw);return bw;... }我們發現這三個實例化方式,最后都會走 getInstantiationStrategy().instantiate(...),見實現類 SimpleInstantiationStrategy.instantiate:
public Object instantiate ... {if (bd.getMethodOverrides().isEmpty()) {...return BeanUtils.instantiateClass(constructorToUse);}else {// Must generate CGLIB subclass.return instantiateWithMethodInjection(bd, beanName, owner);} }雖然拿到了構造函數,并沒有立即實例化。因為用戶使用了 replace 和 lookup 的配置方法,用到了動態代理加入對應的邏輯。如果沒有的話,直接使用反射來創建實例。創建實例后,就可以開始注入屬性和初始化等操作。但這里的 Bean 還不是最終的 Bean。返回給調用方使用時,如果是 FactoryBean 的話需要使用 getObject 方法來創建實例。見 AbstractBeanFactory.getObjectFromBeanInstance ,會執行到 doGetObjectFromFactoryBean :
private Object doGetObjectFromFactoryBean ... {...object = factory.getObject();...return object; }3.5. 注入屬性
實例創建完后開始進行屬性的注入,如果涉及到外部依賴的實例,會自動檢索并關聯到該當前實例。Ioc 思想體現出來了。正是有了這一步操作,Spring 降低了各個類之間的耦合。屬性填充的入口方法在AbstractAutowireCapableBeanFactory.populateBean。
protected void populateBean ... {PropertyValues pvs = mbd.getPropertyValues();...// InstantiationAwareBeanPostProcessor 前處理for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {continueWithPropertyPopulation = false;break;}}}...// 根據名稱注入if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// 根據類型注入if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}... // InstantiationAwareBeanPostProcessor 后處理for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvs == null) {return;}}}...// 應用屬性值applyPropertyValues(beanName, mbd, bw, pvs); }可以看到主要的處理環節有:
- 應用 InstantiationAwareBeanPostProcessor 處理器,在屬性注入前后進行處理。假設我們使用了 @Autowire 注解,這里會調用到 AutowiredAnnotationBeanPostProcessor 來對依賴的實例進行檢索和注入的,它是 InstantiationAwareBeanPostProcessor 的子類。
- 根據名稱或者類型進行自動注入,存儲結果到 PropertyValues 中。
- 應用 PropertyValues,填充到 BeanWrapper。這里在檢索依賴實例的引用的時候,會遞歸調用 BeanFactory.getBean 來獲得。
3.6. 初始化
3.6.1. 觸發 Aware
如果我們的 Bean 需要容器的一些資源該怎么辦?比如需要獲取到 BeanFactory、ApplicationContext 等等。Spring 提供了 Aware 系列接口來解決這個問題。比如有這樣的 Aware:
- BeanFactoryAware,用來獲取 BeanFactory。
- ApplicationContextAware,用來獲取 ApplicationContext。
- ResourceLoaderAware,用來獲取 ResourceLoaderAware。
- ServletContextAware,用來獲取 ServletContext。
Spring 在初始化階段,如果判斷 Bean 實現了這幾個接口之一,就會往 Bean 中注入它關心的資源。見 AbstractAutowireCapableBeanFactory.invokeAwareMethos :
private void invokeAwareMethods(final String beanName, final Object bean) {if (bean instanceof Aware) {if (bean instanceof BeanNameAware) {((BeanNameAware) bean).setBeanName(beanName);}if (bean instanceof BeanClassLoaderAware) {((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());}if (bean instanceof BeanFactoryAware) {((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);}} }3.6.2. 觸發 BeanPostProcessor
在 Bean 的初始化前或者初始化后,我們如果需要進行一些增強操作怎么辦?這些增強操作比如打日志、做校驗、屬性修改、耗時檢測等等。Spring 框架提供了 BeanPostProcessor 來達成這個目標。比如我們使用注解 @Autowire 來聲明依賴,就是使用 AutowiredAnnotationBeanPostProcessor 來實現依賴的查詢和注入的。接口定義如下:
public interface BeanPostProcessor {// 初始化前調用Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;// 初始化后調用Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}實現該接口的 Bean 都會被 Spring 注冊到 beanPostProcessors 中,見 AbstractBeanFactory :
/** BeanPostProcessors to apply in createBean */ private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();只要 Bean 實現了 BeanPostProcessor 接口,加載的時候會被 Spring 自動識別這些 Bean,自動注冊,非常方便。然后在 Bean 實例化前后,Spring 會去調用我們已經注冊的 beanPostProcessors 把處理器都執行一遍。
public abstract class AbstractAutowireCapableBeanFactory ... {...@Overridepublic Object applyBeanPostProcessorsBeforeInitialization ... {Object result = existingBean;for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {result = beanProcessor.postProcessBeforeInitialization(result, beanName);if (result == null) {return result;}}return result;}@Overridepublic Object applyBeanPostProcessorsAfterInitialization ... {Object result = existingBean;for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {result = beanProcessor.postProcessAfterInitialization(result, beanName);if (result == null) {return result;}}return result;}... }這里使用了責任鏈模式,Bean 會在處理器鏈中進行傳遞和處理。當我們調用 BeanFactory.getBean 的后,執行到 Bean 的初始化方法 AbstractAutowireCapableBeanFactory.initializeBean 會啟動這些處理器。
protected Object initializeBean ... { ...wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);...// 觸發自定義 init 方法invokeInitMethods(beanName, wrappedBean, mbd);...wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);... }3.6.3. 觸發自定義 init
自定義初始化有兩種方式可以選擇:
- 實現 InitializingBean。提供了一個很好的機會,在屬性設置完成后再加入自己的初始化邏輯。
- 定義 init 方法。自定義的初始化邏輯。
見 AbstractAutowireCapableBeanFactory.invokeInitMethods :
protected void invokeInitMethods ... {boolean isInitializingBean = (bean instanceof InitializingBean);if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {...((InitializingBean) bean).afterPropertiesSet();...}if (mbd != null) {String initMethodName = mbd.getInitMethodName();if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&!mbd.isExternallyManagedInitMethod(initMethodName)) {invokeCustomInitMethod(beanName, bean, mbd);}}}3.7. 類型轉換
Bean 已經加載完畢,屬性也填充好了,初始化也完成了。在返回給調用者之前,還留有一個機會對 Bean 實例進行類型的轉換。見 AbstractBeanFactory.doGetBean :
protected <T> T doGetBean ... {...if (requiredType != null && bean != null && !requiredType.isInstance(bean)) {...return getTypeConverter().convertIfNecessary(bean, requiredType);...}return (T) bean; }4. 總結
拋開一些細節處理和擴展功能,一個 Bean 的創建過程無非是:獲取完整定義 -> 實例化 -> 依賴注入 -> 初始化 -> 類型轉換。
作為一個完善的框架,Spring 需要考慮到各種可能性,還需要考慮到接入的擴展性。所以有了復雜的循環依賴的解決,復雜的有參數構造器的匹配過程,有了 BeanPostProcessor 來對實例化或初始化的 Bean 進行擴展修改。
先有個整體設計的思維,再逐步擊破針對這些特殊場景的設計,整個 Bean 加載流程迎刃而解。
總結
以上是生活随笔為你收集整理的Spring框架—SpringBean加载过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring框架—SpringBean配
- 下一篇: Spring框架—SpringBean源