­

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 FactoryBeanBeanFactoryUtils.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的內容印證完畢,本文結束。