聊聊Spring的FactoryBean其實沒那麼難

愛研究原始碼的javaer發表於2020-12-15

前言

談到Spring的FactoryBean,就會知道Spring中經典的面試題:FactoryBean和BeanFactory的區別。我們這裡就簡單概括下: 、

  1. BeanFactory是介面,提供了OC容器最基本的形式,給具體的IOC容器的實現提供了規範,FactoryBean也是介面,為IOC容器中Bean的實現提供了更加靈活的方式,FactoryBean在IOC容器的基礎上給Bean的實現加上了一個簡單工廠模式和裝飾模式(如果想了解裝飾模式參考:修飾者模式(裝飾者模式,Decoration) 我們可以在getObject()方法中靈活配置。其實在Spring原始碼中有很多FactoryBean的實現類.

  2. BeanFactory是個Factory,也就是IOC容器或物件工廠,FactoryBean是個Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)來進行管理的。但對FactoryBean而言,這個Bean不是簡單的Bean,而是一個能生產或者修飾物件生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似

示例

首先我們定義個普通的java pojo物件名叫Car

public class Car {

   private   int maxSpeed ;
   private  String brand ;
   private   double price ;

   public double getPrice() {
         return price;
   }

   public int getMaxSpeed() {
      return maxSpeed;
   }

   public String getBrand() {
      return brand;
   }

   public void setBrand(String brand) {
      this.brand = brand;
   }

   public void setMaxSpeed(int maxSpeed) {
      this.maxSpeed = maxSpeed;
   }

   public void setPrice(double price) {
      this.price = price;
   }

   @Override
   public String toString() {
      return "Car{" +
            "maxSpeed=" + maxSpeed +
            ", brand='" + brand + '\'' +
            ", price=" + price +
            '}';
   }
}

再定義一個實現FactoryBean介面的實現類CarFactoryBean

public class CarFactoryBean  implements FactoryBean<Car> {
   private  String carInfo;

   public  Car getObject()  throws  Exception {

      System.out.println("start new car");
      Car car = new Car();
      String[] infos = carInfo.split(",");
      car.setBrand(infos[0]);
      car.setMaxSpeed(Integer.valueOf(infos[1]));
      car.setPrice(Double.valueOf(infos[2]));
      return car;
   }

   public  Class<Car> getObjectType(){
         return  Car.class ;

   }

   //返回的例項是同一個
   public   boolean  isSingleton()   {
         return   true;
      }

      public  String getCarInfo()  {
      return   this.carInfo ;
   }

      public   void  setCarInfo(String carInfo)   {
         this.carInfo = carInfo;
   }
}

再來看看我們的Xml配置是如何配置Bean的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:aop="http://www.springframework.org/schema/aop"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
   <bean id="car" name="car1 car2" class="com.john.factorybean.CarFactoryBean">
   <property name="carInfo" value="超級跑車,400,2000000"/>
   </bean>
</beans>

最後我們載入這個配置檔案,啟動Spring容器來獲取car這個Bean

public static void main( String[] args ) {
      ClassPathXmlApplicationContext ac =new ClassPathXmlApplicationContext();
      ac.setConfigLocations("spring-config-locatorfactory.xml");
      ac.refresh();
      //getBean的時候才會去getObject
      ac.getBean("car", Car.class);
   }

成功列印出

start new car
Car{maxSpeed=400, brand='超級跑車', price=2000000.0}

原理

如果我們把ac.getBean("car", Car.class);這行程式碼去掉,我們會發現start new car這行是不會列印的,那就是說明getObject是延遲載入的,只有顯示呼叫才能觸發,spring在初始化啟動單例bean例項化的時候並不會觸發(SmartFactoryBean除外,之後我們會單獨分析下SmartFactoryBean)

那我們就到原始碼級別證明下Spring的FactoryBean的getObject是延遲初始化的

DefaultListableBeanFactory

preInstantiateSingletons

@Override
public void preInstantiateSingletons() throws BeansException {
    
   // Iterate over a copy to allow for init methods which in turn register new bean definitions.
   // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
   List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

   // Trigger initialization of all non-lazy singleton beans...
   //todo 遍歷所有不是延遲初始化的 單例 bean 2020-08-28
   for (String beanName : beanNames) {
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      //不是抽象的 且是單例的 且不是懶載入的
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
         //todo 如果是factorybean 那還得去判斷 是不是eagerInit
         if (isFactoryBean(beanName)) {
            Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
            if (bean instanceof FactoryBean) {
               final FactoryBean<?> factory = (FactoryBean<?>) bean;
               //判斷是不是渴望初始化的。。
               boolean isEagerInit;
               if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                  isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                              ((SmartFactoryBean<?>) factory)::isEagerInit,
                        getAccessControlContext());
               }
               else {
                  isEagerInit = (factory instanceof SmartFactoryBean &&
                        ((SmartFactoryBean<?>) factory).isEagerInit());
               }
               if (isEagerInit) {
                  getBean(beanName);
               }
            }
         }
         else {
            getBean(beanName);
         }
      }
   }
}

我們看到Spring會在例項化單例Bean的時候會去判斷是否是Factorybean,如果是的話就會去根據FACTORY_BEAN_PREFIX + beanName獲取Factorybean本身,然後再判斷這個FactoryBean是SmartFactoryBean且isEagerInit設定為true才會去獲取beanName對應的Bean,也就是例項化這個bean。

那既然是延遲初始化的,那我們再獲取實際bean的時候Spring內部是怎麼做的呢?

AbstractBeanFactory

doGetBean

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

				// Create bean instance.
				//從頭開始載入 bean,這個過程由 getSingleton() 實現。
				if (mbd.isSingleton()) {
					//https://www.cnblogs.com/leihuazhe/p/9481018.html
					////todo 如果有單例那直接返回了 , args引數也就沒啥用處了 2020-08-30
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly(顯式) remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
}

這裡我們從getObjectForBeanInstance方法可以看出來這裡就是獲取真正bean的方法。

getObjectForBeanInstance

protected Object getObjectForBeanInstance(
      Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
  
  	// Now we have the bean instance, which may be a normal bean or a FactoryBean.
		// If it's a FactoryBean, we use it to create a bean instance, unless the
		// caller actually wants a reference to the factory.
		//我們使用它來建立一個bean例項,除非呼叫者實際上想要引用工廠
		//todo 如果是獲取FactoryBean本身 那就直接返回這個工廠
		if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
			return beanInstance;
		}
}

也就是說不是FactoryBean本身或者根據name判斷就是獲取FactoryBean本身的話,那就直接返回這個beanInstance。

那我們在getBean("car")的時候其實獲取到的beanInstance是FactoryBean的例項,所以走到這邊的時候不會返回。

下面Spring就會走到抽象類FactoryBeanRegistrySupport中的getObjectFromFactoryBean函式,因為AbstractBeanFactory繼承自FactoryBeanRegistrySupport,從名字就可以看出這個函式的意義就是呼叫FactoryBean的getObject方法。

FactoryBeanRegistrySupport

getObjectFromFactoryBean

/**
 * Obtain an object to expose from the given FactoryBean.
 * @param factory the FactoryBean instance
 * @param beanName the name of the bean
 * @param shouldPostProcess whether the bean is subject to post-processing
 * @return the object obtained from the FactoryBean
 * @throws BeanCreationException if FactoryBean object creation failed
 * @see org.springframework.beans.factory.FactoryBean#getObject()
 */
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {

   if (factory.isSingleton() && containsSingleton(beanName)) {
      synchronized (getSingletonMutex()) {
         Object object = this.factoryBeanObjectCache.get(beanName);
         if (object == null) {
            object = doGetObjectFromFactoryBean(factory, beanName);
            // Only post-process and store if not put there already during getObject() call above
            // (e.g. because of circular reference processing triggered by custom getBean calls)
            Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
            if (alreadyThere != null) {
               object = alreadyThere;
            }
            else {
               if (shouldPostProcess) {
                  if (isSingletonCurrentlyInCreation(beanName)) {
                     // Temporarily(暫時) return non-post-processed object, not storing it yet..
                     return object;
                  }
                  beforeSingletonCreation(beanName);
                  try {
                     object = postProcessObjectFromFactoryBean(object, beanName);
                  }
                  catch (Throwable ex) {
                     throw new BeanCreationException(beanName,
                           "Post-processing of FactoryBean's singleton object failed", ex);
                  }
                  finally {
                     afterSingletonCreation(beanName);
                  }
               }
              	//object放入factoryBeanObjectCache快取
               if (containsSingleton(beanName)) {
                  this.factoryBeanObjectCache.put(beanName, object);
               }
            }
         }
         return object;
      }
   }
   else {
      //非單例
      省略部分程式碼
   }
}

函式內部還會呼叫doGetObjectFromFactoryBean函式

doGetObjectFromFactoryBean

/**
 * Obtain an object to expose from the given FactoryBean.
 * @param factory the FactoryBean instance
 * @param beanName the name of the bean
 * @return the object obtained from the FactoryBean
 * @throws BeanCreationException if FactoryBean object creation failed
 * @see org.springframework.beans.factory.FactoryBean#getObject()
 */
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
      throws BeanCreationException {

   Object object;
   try {
      if (System.getSecurityManager() != null) {
         AccessControlContext acc = getAccessControlContext();
         try {
            object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
         }
         catch (PrivilegedActionException pae) {
            throw pae.getException();
         }
      }
      else {
         //最重要的呼叫getObject方法
         object = factory.getObject();
      }
   }
   catch (FactoryBeanNotInitializedException ex) {
      throw new BeanCurrentlyInCreationException(beanName, ex.toString());
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
   }

   // Do not accept a null value for a FactoryBean that's not fully
   // initialized yet: Many FactoryBeans just return null then.
   if (object == null) {
      if (isSingletonCurrentlyInCreation(beanName)) {
         throw new BeanCurrentlyInCreationException(
               beanName, "FactoryBean which is currently in creation returned null from getObject");
      }
      object = new NullBean();
   }
   return object;
}

從這個函式中我們看到了FactoryBean自身的getObject方法。呼叫完getObject方法後,Spring就會把這個物件放入factoryBeanObjectCache快取物件中。之後再獲取的時候直接從factoryBeanObjectCache快取中拿就是了。

迎搜尋關注我的公眾號:碼農約翰的沉思錄,瞭解更多Spring原理

相關文章