Spring中FactoryBean的作用和實現原理
- 2019 年 12 月 12 日
- 筆記
BeanFactory與FactoryBean,相信很多剛翻看Spring源碼的同學跟我一樣很好奇這倆貨怎麼長得這麼像,分別都是幹啥用的。BeanFactory是Spring中Bean工廠的頂層介面,也是我們常說的SpringIOC容器,它定下了IOC容器的一些規範和常用方法並管理著Spring中所有的Bean,今天我們不講它,我們看一下後面那個FactoryBean。
先說下FactoryBean和其作用再開始分析:首先它是一個Bean,但又不僅僅是一個Bean。它是一個能生產或修飾對象生成的工廠Bean,類似於設計模式中的工廠模式和裝飾器模式。它能在需要的時候生產一個對象,且不僅僅限於它自身,它能返回任何Bean的實例。
上面的解釋有點抽象,那麼我們瀏覽一遍FactoryBean在Spring中是怎麼應用的就好懂了。我們都知道在Spring中我們的Bean都會被Spring的IOC容器所管理,在AbstractApplicationContext中有一個很重要的方法:refresh(),項目啟動或重啟的時候refresh()會調用getBean()初始化所有的Bean,這個getBean()最終會指向AbstractBeanFactory中的getBean()方法。
在AbstractBeanFactory中,不管是根據名稱還是根據類型,getBean()最終都會調用doGetBean(),在doGetBean()方法中一開始就獲取了名稱beanName和實例sharedInstance,這個方法太長,這裡就貼前面兩行。
String beanName = this.transformedBeanName(name); Object sharedInstance = this.getSingleton(beanName);
transformedBeanName(name)是為了獲取Bean真正的名稱,它會去掉name前面的'&'
,而getSingleton(beanName)是從父類容器singletonObjects中取的這個Bean的實例。在Spring中還有很多這樣的容器,比如DefaultListableBeanFactory中的beanDefinitionMap,它就是的IOC容器真正保存Bean的地方,它是一個HashMap。類似的還有FactoryBeanRegistrySupport中的factoryBeanObjectCache等。
回到doGetBean()方法中,拿到sharedInstance後,後面的一大堆操作做了單例、多例等判斷,最終會走到this.getObjectForBeanInstance(),關鍵部分就在這個方法中,進入方法程式碼。
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { if (BeanFactoryUtils.isFactoryDereference(name)) { if (beanInstance instanceof NullBean) { return beanInstance; } if (!(beanInstance instanceof FactoryBean)) { throw new BeanIsNotAFactoryException(this.transformedBeanName(name), beanInstance.getClass()); } } if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) { Object object = null; if (mbd == null) { object = this.getCachedObjectForFactoryBean(beanName); } if (object == null) { FactoryBean<?> factory = (FactoryBean)beanInstance; if (mbd == null && this.containsBeanDefinition(beanName)) { mbd = this.getMergedLocalBeanDefinition(beanName); } boolean synthetic = mbd != null && mbd.isSynthetic(); object = this.getObjectFromFactoryBean(factory, beanName, !synthetic); } return object; } else { return beanInstance; } }
在上面的程式碼中有兩個判斷分別是beanInstance instanceof FactoryBean
和BeanFactoryUtils.isFactoryDereference(name)
,前面判斷的是beanInstance是否屬於FactoryBean或其子類的實例,後面這個方法判斷name是否不為空且以&
開頭。
public static boolean isFactoryDereference(@Nullable String name) { return name != null && name.startsWith("&"); }
如果beanInstance不屬於FactoryBean或其子類的實例,或者name是以&
開頭就直接返回實例對象beanInstance,否則進入到if分支中。在if分支里的各種if .. ==null
判斷是為了提高性能,咱們只挑關鍵部分看:object = this.getObjectFromFactoryBean(factory, beanName, !synthetic);
繼續跟蹤進去看程式碼。
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) { if (factory.isSingleton() && this.containsSingleton(beanName)) { synchronized(this.getSingletonMutex()) { Object object = this.factoryBeanObjectCache.get(beanName); if (object == null) { object = this.doGetObjectFromFactoryBean(factory, beanName); Object alreadyThere = this.factoryBeanObjectCache.get(beanName); if (alreadyThere != null) { object = alreadyThere; } else { if (shouldPostProcess) { if (this.isSingletonCurrentlyInCreation(beanName)) { return object; } this.beforeSingletonCreation(beanName); try { object = this.postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable var14) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's singleton object failed", var14); } finally { this.afterSingletonCreation(beanName); } } if (this.containsSingleton(beanName)) { this.factoryBeanObjectCache.put(beanName, object); } } } return object; } } else { Object object = this.doGetObjectFromFactoryBean(factory, beanName); if (shouldPostProcess) { try { object = this.postProcessObjectFromFactoryBean(object, beanName); } catch (Throwable var17) { throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", var17); } } return object; } }
這裡面也是做了很多判斷,咱們也只挑關鍵部分看。這裡說一下個人想法,看源碼的時候如果我們一直追究所有的細節那會讓我們會越陷越深,掉入細節的無底洞,稍不留神腦迴路跟不上就會蒙圈。我們要學會找源碼中的關鍵部分看,弄懂主要流程和本次看源碼的目的的那部分就行。等我們對Spring整體有了一個很好的理解之後,再回頭看之前不懂的程式碼就會豁然開朗。在上面這個方法中不管是走上面的if分支還是到下邊的else中,關鍵部分就是object = this.doGetObjectFromFactoryBean(factory, beanName)
這段程式碼調用,繼續點進去。
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException { Object object; try { if (System.getSecurityManager() != null) { AccessControlContext acc = this.getAccessControlContext(); try { object = AccessController.doPrivileged(factory::getObject, acc); } catch (PrivilegedActionException var6) { throw var6.getException(); } } else { object = factory.getObject(); } } catch (FactoryBeanNotInitializedException var7) { throw new BeanCurrentlyInCreationException(beanName, var7.toString()); } catch (Throwable var8) { throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", var8); } if (object == null) { if (this.isSingletonCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject"); } object = new NullBean(); } return object; }
在這個方法中有一行關鍵程式碼:object = factory.getObject();
這個factory就是我們傳入的beanInstance
實例。繞了這麼一大圈,getBean方法返回的居然是我們實現FactoryBean介面定義的getObject方法。
那麼跟一開始對FactoryBean的描述印證了,FactoryBean是一個能生產或修飾對象生成的工廠Bean。一個Bean如果實現了FactoryBean介面,那麼根據該Bean的名稱獲取到的實際上是getObject()返回的對象,而不是這個Bean自身實例,如果要獲取這個Bean自身實例,那麼需要在名稱前面加上』&』符號。
一般情況下,Spring通過反射機制利用的class屬性指定實現類實例化Bean,在某些情況下,實例化Bean過程比較複雜,如果按照傳統的方式,則需要在中提供大量的配置資訊。配置方式的靈活性是受限的,這時採用編碼的方式可能會得到一個簡單的方案。Spring為此提供了一個org.springframework.bean.factory.FactoryBean的工廠類介面,用戶可以通過實現該介面訂製實例化Bean的邏輯。FactoryBean介面對於Spring框架來說佔用重要的地位,Spring自身就提供了70多個FactoryBean的實現。它們隱藏了實例化一些複雜Bean的細節,給上層應用帶來了便利。從Spring3.0開始,FactoryBean開始支援泛型,即介面聲明改為FactoryBean的形式
原理弄明白了,下面通過程式碼測試驗證上面的流程,先定義一個Bean實現FactoryBean介面。
@Component public class MyBean implements FactoryBean { private String message; public MyBean() { this.message = "通過構造方法初始化實例"; } @Override public Object getObject() throws Exception { MyBean myBean = new MyBean(); myBean.message = "通過FactoryBean.getObject()創建實例"; // 這裡並不一定要返回MyBean自身的實例,可以是其他任何對象的實例 return myBean; } @Override public Class<?> getObjectType() { return MyBean.class; } public String getMessage() { return message; } }
MyBean實現了FactoryBean介面的兩個方法,getObject()是可以返回任何對象的實例的,這裡測試就返回MyBean自身實例,且返回前給message欄位賦值。同時在構造方法中也為message賦值。然後測試程式碼中先通過名稱獲取Bean實例,列印message的內容,再通過』&』+名稱獲取實例並列印message內容。
@RunWith(SpringRunner.class) @SpringBootTest(classes = TestApplication.class) public class FactoryBeanTest { @Autowired private ApplicationContext context; @Test public void test() { MyBean myBean1 = (MyBean) context.getBean("myBean"); System.out.println("myBean1 = " + myBean1.getMessage()); MyBean myBean2 = (MyBean) context.getBean("&myBean"); System.out.println("myBean2 = " + myBean2.getMessage()); System.out.println("myBean1.equals(myBean2) = " + myBean1.equals(myBean2)); } }
myBean1 = 通過FactoryBean.getObject()初始化實例 myBean2 = 通過構造方法初始化實例 myBean1.equals(myBean2) = false
通過測試我們發現獲取的兩個實例中的message的值不一樣,對比兩個對象的引用也不相同。上述所講關於FactoryBean的內容印證完畢,本文結束。