Spring 獲取單例流程(一)

讀完這篇文章你將會收穫到

  • getBean方法中, Spring處理別名以及 factoryBeanname
  • Spring如何從多級緩存中根據 beanName獲取 bean
  • Spring如何處理用戶獲取普通 beanfactoryBean

引言

Spring 容器的初始化 中,我們瞭解到 Spring是如何將 XML文件轉換爲 BeanDefinition並註冊到 BeanDefinitionRegstry

今天我們一起繼續學習 Springbean加載

public static void main(String[] args) {
		Resource resource = new ClassPathResource("coderLi.xml");
		DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
		XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
		xmlBeanDefinitionReader.loadBeanDefinitions(resource);
	}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
	<bean class="com.demo.data.Person">
		<description>
			微信搜一搜:CoderLi
		</description>
	</bean>
</beans>

熟悉的味道、熟悉的配方

源碼分析

我們可以在上面的 Java 代碼中加入以下的代碼

System.out.println(defaultListableBeanFactory.getBean("com.demo.data.Person#0"));

我們根據默認的 beanName 從 Spring 容器中獲取 Person 這個 bean 對象,當然我們可以使用默認的別名獲取

System.out.println(defaultListableBeanFactory.getBean("com.demo.data.Person"));

對 Spring 別名不熟悉的朋友可以先看下我的這一篇文章 Spring-AliasRegistry

我們直接進入到 AbstractBeanFactory#getBean(String)方法中, AbstractBeanFactoryDefaultListableBeanFactory的父類

@Override
public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}

可以看到 do開頭的纔是真正幹活的老大 , AbstractBeanFactory#doGetBean

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

   // 找到這個參數的 bean name
   final String beanName = transformedBeanName(name);
   Object bean;

   // Eagerly check singleton cache for manually registered singletons.
   // 檢查緩衝中是否有這個bean、spring中只是保存單例的bean
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
      // 這裏被我刪除了一些spring  的log
      // 處理一下 factory bean 的情況、包括從 factory beans 的緩存中獲取、或者重新調用 factory bean 的 get bean 方法 包括一些回調
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }
 ..........
 ...........

因爲這個方法實在太長了所以截取一部分、我們一步步來分析

transformedBeanName(name)這個方法就是將我們的 name 轉換爲真正的 beanName,因爲我們傳進來的參數可能是一個 alias 或者可能是一個 factoryBean 的 beanName (前綴爲&),而我們在 Spring 中存放的 factoryBean 的 beanName 是沒有 & 前綴的,所以需要處理掉這個前綴

protected String transformedBeanName(String name) {
   return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
public static String transformedBeanName(String name) {
		Assert.notNull(name, "'name' must not be null");
		// 是否是一個 factory bean 、如果不是的話就直接返回
		if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
			return name;
		}
		// 如果是的話就將其前綴 & 去掉
		return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
			do {
				beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
			}
			while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
			return beanName;
		});
	}
	/**
	 * 找到這個別名的最終的 bean Name、如果沒有的話(
	 * 也就是說參數中的name 就是人家的 bean name 那麼就直接返回這個 參數就行了)
	 *
	 */
	public String canonicalName(String name) {
		String canonicalName = name;
		// Handle aliasing...
		String resolvedName;
		do {
			resolvedName = this.aliasMap.get(canonicalName);
			if (resolvedName != null) {
				canonicalName = resolvedName;
			}
		}
		while (resolvedName != null);
		return canonicalName;
	}

讓我們再看看下一個方法 DefaultSingletonBeanRegistry#getSingleton(String)

public Object getSingleton(String beanName) {
   // allowEarlyReference 允許早期依賴
   return getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {

   Object singletonObject = this.singletonObjects.get(beanName);
   // 這個bean 正處於 創建階段
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      // 併發控制
      synchronized (this.singletonObjects) {
         // 單例緩存是否存在
         singletonObject = this.earlySingletonObjects.get(beanName);
         // 是否運行獲取 bean factory 創建出的 bean
         if (singletonObject == null && allowEarlyReference) {
            // 獲取緩存中的 ObjectFactory
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               // 將對象緩存到 earlySingletonObject中
               this.earlySingletonObjects.put(beanName, singletonObject);
               // 從工廠緩衝中移除
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

上面的代碼就是 Spring 嘗試從緩存中加載單例。單例在 Spring 的同一個容器中只會被創建一次,後續再獲取 bean,就直接從緩存中取了。

在介紹這個方法之前、我們先認識下 DefaultSingletonBeanRegistry這個類裏面的成員變量吧

  • Map<String, Object> singletonObjects這個很好理解、 key 就是 beanName ,value 就是 bean 實例
  • Map<String, ObjectFactory<?>> singletonFactorieskey 爲 beanName,value 爲創建 bean 的工廠
  • Map<String, Object> earlySingletonObjectskey 爲 beanName ,value 爲 bean。但是和 singletonObjects不同的是,bean 被加入到 earlySingletonObjects的時候、這個 bean 還是處於一種創建中的狀態,目的也很簡單、Spring 用來解決某些場景下的循環依賴

我們再回到代碼中、分析一下它的邏輯

  1. 先從 singletonObjects 中嘗試獲取 bean,這裏存放的是已經創建好的 bean 了、如果在這裏能知道、那當然是最好啦
  2. 如果在這裏找不到的話、那麼我們就要判斷下這個 beanName 對應的 bean 是否正在創建中
  3. 如果是的話,那麼我們再看看這個正在創建的 bean 是否已經曝光出來、如果沒有的話、那麼就要看看我們的參數是否允許依賴早期的 bean 了、
  4. 如果允許早期依賴、那麼我們就嘗試衝 ObjectFactory 中獲取到對應的 bean、並將它放入到 earlySingletonObjects 中、並從 singletonFactories 中移除

類似多級緩存的設計

在上面的方法中我們看到 isSingletonCurrentlyInCreation(beanName)這個方法、

public boolean isSingletonCurrentlyInCreation(String beanName) {
   return this.singletonsCurrentlyInCreation.contains(beanName);
}

singletonsCurrentlyInCreation這個 Set 中,當創建一個 bean 之前會將其 對應的 beanName 放置到這個 Set 中、後面的分析會涉及到、這裏先提一嘴

debug

我們第一次獲取這個 bean 、返回爲 null 是正常的

那假如我們在代碼中 getBean 了兩次

defaultListableBeanFactory.getBean("com.demo.data.Person#0")
defaultListableBeanFactory.getBean("com.demo.data.Person#0")

那麼針對第二次的調用、返回的值就不是爲 null 了

 Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
      // 處理一下 factory bean 的情況、包括從 factory beans 的緩存中獲取、或者重新調用 factory bean 的 get bean 方法 包括一些回調
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }

我們先假設 sharedInstance 不爲 null 也就是我們第二次調用 getBean,我們進入到 getObjectForBeanInstance方法中

protected Object getObjectForBeanInstance(
      Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

    // 我王大錘就是想要一個 factory bean
   if (BeanFactoryUtils.isFactoryDereference(name)) {

      // 如果這個是 NullBean 類型、表示這是一個 null 的 instance、直接返回
      if (beanInstance instanceof NullBean) {
         return beanInstance;
      }
      // 獲取到的 beanInstance 不是一個 factory、但是你tm name 又帶有這個 &  很迷惑啊兄弟
      if (!(beanInstance instanceof FactoryBean)) {
         throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
      }

      if (mbd != null) {
         mbd.isFactoryBean = true;
      }
      return beanInstance;
   }

    // 王大錘不想要factory bean、並且spring 也幫他找到了一個普通的 bean、直接返回
   if (!(beanInstance instanceof FactoryBean)) {
      return beanInstance;
   }

   // 王大錘要的是一個普通的bean 、但是spring 給他找到了一個 factory的bean、那麼spring 是不是要做一些額外的處理 給王大錘返回一個普通的bean
   Object object = null;

   if (mbd != null) {
      mbd.isFactoryBean = true;
   } else {
      // 從緩存中 看看有沒有
      object = getCachedObjectForFactoryBean(beanName);
   }

   // 如果 bean factory 中還是沒有
   if (object == null) {
      // Return bean instance from factory.
      FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
      // 從 緩存中拿到 bean definition
      if (mbd == null && containsBeanDefinition(beanName)) {
         mbd = getMergedLocalBeanDefinition(beanName);
      }
      // 是否是用戶定義的還是程序本身需要創建的bean
      boolean synthetic = (mbd != null && mbd.isSynthetic());

      object = getObjectFromFactoryBean(factory, beanName, !synthetic);
   }
   return object;
}

我們按步驟分析下上面的代碼

  1. 我們調用 getBean(name)中的 name 如果包含前綴 & ,表面我們是想要從 Spring 中獲取一個 FactoryBean ,那麼我們就要判斷我們從緩存中獲取的 beanInstance 是否是 一個 FactoryBean 、如果是的話就直接返回不是的話就要拋出異常了
  2. 我們想要的是一個非 factoryBean 並且 在 spring 容器中找到了非 factoryBean 的 bean、那麼就直接返回
  3. 我們想要的是一個 非 factoryBean 但是在 spring 容器中找到了一個 factoryBean 的 bean、那麼就要進入到 getObjectFromFactoryBean方法中了
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
      // 爲單例模式且緩存中存在
   if (factory.isSingleton() && containsSingleton(beanName)) {

      synchronized (getSingletonMutex()) {
         // 從緩存中獲取指定的 bean(這個bean 是從 factory bean 創建出來的)
         Object object = this.factoryBeanObjectCache.get(beanName);

         if (object == null) {
            // 爲空則從 factory bean 中獲取對象
            object = doGetObjectFromFactoryBean(factory, beanName);
            // 從緩存中獲取
            Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
            if (alreadyThere != null) {
               // 已經存放到 緩存中了、後續的操作就不需要了
               object = alreadyThere;
            } else {
               // 需要做一些後置處理
               if (shouldPostProcess) {
                  // 如果這個bean正在創建中、
                  if (isSingletonCurrentlyInCreation(beanName)) {
                     return object;
                  }
                  // 前置處理 主要是將這個bean 加入到正在創建中的隊列 singletonsCurrentlyInCreation
                  beforeSingletonCreation(beanName);
                  try {
                     // 對 從 factoryBean 獲取的對象進行後處理
                     // 生成對象將暴露給 bean 引用 並回調 beanPostProcessor
                     object = postProcessObjectFromFactoryBean(object, beanName);
                  } catch (Throwable ex) {
                     throw new BeanCreationException(beanName,
                           "Post-processing of FactoryBean's singleton object failed", ex);
                  } finally {
                     // 後置處理 將其從 singletonsCurrentlyInCreation 移除
                     afterSingletonCreation(beanName);
                  }
               }
               // 他的 factory bean 已經存在 緩存中了、那麼這個 factory bean 產生的bean 應該也要緩存一下
               if (containsSingleton(beanName)) {
                  this.factoryBeanObjectCache.put(beanName, object);
               }
            }
         }

         return object;
      }
   } else {
      // 非單例
      Object object = doGetObjectFromFactoryBean(factory, beanName);

      if (shouldPostProcess) {
         try {
            //
            object = postProcessObjectFromFactoryBean(object, beanName);
         } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
         }
      }
      return object;
   }
}

啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、代碼很長長長…我們一點點來分析

  1. 第一步就是判斷這個 factoryBean 是否是單例、如果不是的話,並且是用戶自己定義的 bean、那麼就需要調用 postProcessObjectFromFactoryBean方法去做一個後續的處理

    1. 這裏面最終回調的就是我們常用的一個接口 BeanPostProcessor
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
       	throws BeansException {
    
       Object result = existingBean;
       for (BeanPostProcessor processor : getBeanPostProcessors()) {
       	Object current = processor.postProcessAfterInitialization(result, beanName);
       	if (current == null) {
       		return result;
       	}
       	result = current;
       }
       return result;
    }
    
  2. 如果這 beanFactory 是一個單例,那我們就看看 factoryBeanObjectCache ( key 是 beanName,value 是 beanFactory 產生出來的 object 也是我們正要獲取的 bean ) 這個 Map 中是否存在這個 beanName 這個 bean

  3. 如果存在的話、就直接返回、如果不存在的話、那就 doGetObjectFromFactoryBean,從這個方法中使用 FactoryBean#getObject 產生 bean

  4. 其實下面這段代碼確實讓人看不懂哦

    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
    if (alreadyThere != null) {
    // 已經存放到 緩存中了、後續的操作就不需要了
    object = alreadyThere;
    }
    
  5. 然後我們看到 beforeSingletonCreation這個方法、就是上面 getSingletonisSingletonCurrentlyInCreation判斷一個 bean 是否處於正在創建中

    protected void beforeSingletonCreation(String beanName) {
    		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    			throw new BeanCurrentlyInCreationException(beanName);
    		}
    	}
    	public boolean isSingletonCurrentlyInCreation(String beanName) {
    		return this.singletonsCurrentlyInCreation.contains(beanName);
    	}
    
  6. 然後又調用到 postProcessObjectFromFactoryBean方法、最終回調的就是我們常用的一個接口 BeanPostProcessor

  7. 最好調用 afterSingletonCreation(beanName)方法、將其從 正在創建中的 bean 的集合中移除、最後的最後、將其加入到 factoryBeanObjectCache集合中

今天我們就先分析到這裏、後續的話我們在後面的文章繼續探討。今天我們大致分析了 getBean 裏面的這三個方法

image-20200530152154079

總結

  • 根據參數中的 name 找出對應的 beanName、無論這個 name 是別名或者是一個 factoryBean 的 beanName
  • 查看緩存中是否包含這個 beanName 對象
    • 先從一級緩存 singletonObjects中看看有沒有
    • 然後從二級緩存 earlySingletonObjects
    • 都沒有的話再從三級緩存 singletonFactories中看看有沒有
  • 如果緩存中有 bean、那麼我們還是需要處理一下這個 bean
    • 如果 Spring 緩存中返回的 bean 是 factoryBean、而用戶也想要的是一個 beanFactory (參數 name 中的前綴是 &)、那麼我們直接返回
    • 如果 Spring 緩存中返回的 bean 是普通的 bean、而用戶也想要的是一個普通的 bean 、那麼就直接返回
    • 如果 Spring 緩存中返回的 bean 是一個 factoryBean、而用戶想要的是一個普通的 bean 、那麼我們就要從 factoryBean 中獲取這個 bean
    • 而從 factoryBean 中獲取這個 bean的過程中、需要調用到前置處理、後置處理和我們常用的接口回調 BeanPostProcessor

上面的三個方法大致流程就是這樣、希望對各位有幫助

有興趣進入羣聊、一起交流一起划水

image-20200530170736820

這次一定?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章