bean 的載入
之前文章主要分析了對 XML 配置檔案的解析,接下來就是對 bean 的載入進行分析,同樣開始用最開始的程式碼為入口。
入口程式碼 getBean
public void testSimpleLoad(){
final BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
final MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
assertEquals("testStr",myTestBean.getTestStr());
}
從這裡我們快速先大致瞭解一下是如何實現的。
從 BeanFactory 介面中我們選擇對應實現類為 AbstractBeanFactory。
Object getBean(String name) throws BeansException;
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//通過三種形式獲取beanName, 一個是原始的beanName,一個是加了&的,一個是別名
//返回容器中真是的beanName
final String beanName = transformedBeanName(name);
Object bean;
/**
* 檢查快取中或例項工廠中是否存在對應例項
* 因為在建立單例bean的時候會存在依賴注入的情況,Spring為了避免迴圈依賴,建立bean的原則是不等bean建立完成就會建立bean的ObjectFactory提早曝光
* 也就是將ObjectFactory放入到了快取中,一旦下個bean建立時候需要依賴上一個bean則直接使用ObjectFactory
*/
// Eagerly check singleton cache for manually registered singletons.
//嘗試從快取中獲取或者從singleFactories中的ObjectFactory中獲取
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
//如果Bean還在建立中,則說明是迴圈引用
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
//如果是普通bean,直接返回,如果是FactoryBean,則返回它的getObject
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
//只有在單例情況下Spring才會嘗試解決迴圈依賴,在原型模式下如果存在A->B->A的話就會丟擲異常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
//檢查工廠中是否存在bean定義
BeanFactory parentBeanFactory = getParentBeanFactory();
//如果beanDefinitionMap中不包括當前beanName則會嘗試從parentBeanFactory中檢測
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
//主要針對FactoryBean,將Bean的&重新加上
//將轉換過後的BeanName恢復回原先的樣子
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
//遞迴到BeanFactory中尋找
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
//如果有引數,則委派父類容器根據指定名稱和引數查詢
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
//委派父級容器根據指定名稱和型別查詢
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
//委派父級容器根據指定名稱查詢
return (T) parentBeanFactory.getBean(nameToLookup);
}
}
//如果不是僅僅做型別檢查則是建立bean,進行記錄
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
//將儲存XML配置檔案的GenericBeanDefinition轉換為RootBeanDefinition,如果指定的BeanName是子bean的話還會合並父類的相同屬性
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
//對合並的BeanDefinition做驗證,主要看屬性是否為abstract的
checkMergedBeanDefinition(mbd, beanName, args);
String[] dependsOn = mbd.getDependsOn();
//如果存在依賴則需要遞迴例項化依賴的bean
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
//快取依賴呼叫
registerDependentBean(dep, beanName);
try {
//遞迴呼叫getBean方法,註冊Bean之間的依賴(如C需要晚於B初始化,而B需要晚於A初始化)
//初始化依賴的bean
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// 例項化依賴的bean後就可以例項化mbd本身了
// 如果BeanDefinition為單例
if (mbd.isSingleton()) {
//建立Bean例項物件,並且註冊給所依賴的物件
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
//從單例快取中刪除bean例項
//因為單例模式下為了解決迴圈依賴,可能它已經存在了,所以將其銷燬
destroySingleton(beanName);
throw ex;
}
});
//如果是普通bean,直接返回,如果是FactoryBean,則返回它的getObject
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 如果BeanDefinition為prototype
else if (mbd.isPrototype()) {
// 每次建立一個新的物件
Object prototypeInstance = null;
try {
//註冊當前建立的prototype物件為正在建立中
beforePrototypeCreation(beanName);
//建立原型物件例項
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//將先前註冊的正在建立中的Bean資訊給抹除掉
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
//如果不是原型模式和單例模式則可能是: request、session、application等生命週期
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
//Bean定義資源中沒有配置生命週期範圍,則Bean定義不合法
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
//如果bean的scope不是singleton和prototype,則呼叫scope.get()來選擇合適的載入策略
Object scopedInstance = scope.get(beanName, () -> {
//註冊當前建立的prototype物件為正在建立中
beforePrototypeCreation(beanName);
try {
//建立bean
return createBean(beanName, mbd, args);
}
finally {
//將先前註冊的正在建立中的Bean資訊給抹除掉
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// Check if required type matches the type of the actual bean instance.
//檢查需要的型別是否符合bean的實際型別
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
if (convertedBean == null) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return convertedBean;
}
catch (TypeMismatchException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Failed to convert bean '" + name + "' to required type '" +
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
//返回bean例項
return (T) bean;
}
可以看到該方法程式碼量很多,我們先大概整理一下流程:
1.轉換對應 beanName
轉換對應 beanName 的意思就是,這裡我們傳入的 beanName 可能是別名也可能是 FactoryBean,所以要進行解析。
包括:
- 去除 FactoryBean 的修飾符,也就是如果 name=&jack,那麼會去除&而使 name=jack
- 取指定 alias 所表示的最終 beanName,如果別名 A 指向名稱 B 的 bean,則返回 B 的 bean;如果別名 A 指向別名 B,別名 B 又指向別名 C 則返回 C 的 bean
2.嘗試從快取中載入單例
單例就是在 Spring 的同一容器中只會被建立一次,後續獲取則無需再次建立,直接從單例快取中獲取。但這裡也是嘗試載入 bean,首先嚐試從快取中載入,如果載入不成功則嘗試從 singletonFactories 中載入。因為在建立單例bean的時候可能會存在依賴注入的情況,Spring在建立依賴的時候為了避免迴圈依賴,建立bean的原則是不等bean建立完成就會將建立bean的ObjectFactory提前曝光加入到快取中,一旦下一個bean建立時候需要依賴上一個bean則直接使用ObjectFactory
。
3.bean 的例項化
如果從快取中得到的是未例項化的 bean,則需要進行例項化操作,需要注意:快取中記錄的是最原始的 bean 狀態,不一定是我們想要 bean。我們需要的是工廠 bean 中定義的 factory-method 方法中返回的 bean,而getObjectForBeanInstance
方法就是完成這個工作的。
4.原型模式的依賴檢查
只有單例的情況下才會嘗試解決迴圈依賴,如果存在 A 中有 B 屬性,B 中有 A 屬性,當依賴注入時候就會產生 A 還沒建立完的時候因為對於 B 的建立再次返回建立 A,造成迴圈依賴。也就是isPrototypeCurrentlyInCreation(beanName)
為 true。
5.檢測 parentBeanFactory
BeanFactory parentBeanFactory = getParentBeanFactory();
如果從快取中沒有獲取到,就從父類工廠中去載入。
下一行去進行判斷,如果 parentBeanFactory 為空一切都是浮雲,隨後去檢測如果當前載入的 XML 配置中不包含 beanName 所對應的配置,就只能到 parentBeanFactory 中嘗試,然後再去遞迴呼叫 getBean 方法。
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
6.將儲存 XML 配置檔案的 GenericBeanDefinition 轉換為 RootBeanDefinition
之前說過從 XML 中讀取到的 bean 資訊是存在 GenericBeanDefinition 中的,但是後續的 bean 處理都是針對 RootBeanDefinition 的,所以要進行一個轉換,同時判斷父類 bean 不為空的話,一併合併父類屬性。
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
7.尋找依賴
在 bean 初始化中可能會用到某些屬性,而某些屬性可能是動態配置的,並且依賴其他 bean,這時候需要先載入所依賴的 bean。
8. 針對不同 scope 進行 bean 的建立
該階段對不同的 scope 進行 bean 的建立,預設的是 singleton,還有 prototype、request 等。
9.型別轉換
到這裡基本上 bean 的建立就已經結束了,這一步對 bean 進行一個轉換,判斷requiredType != null
的話就轉換為實際的型別。經過這些步驟後 bean 的建立就完成了,隨後返回我們需要的 bean。
總結
- 通過
getSingleton(beanName)
方法檢查快取中是否存在已經載入的 bean - 如果條件成立
if (sharedInstance != null && args == null)
,呼叫getObjectForBeanInstance(sharedInstance, name, beanName, null)
方法獲取到 bean 例項,但有時存在比如 BeanFactory 的情況並不是直接返回例項本身而是返回指定方法的例項,並跳轉到第 6 步 if (parentBeanFactory != null && !containsBeanDefinition(beanName))
如果當前不存在 beanName,呼叫方法 parentBeanFactory.getBean 去父類工廠中去找,父類工廠通常為空getMergedLocalBeanDefinition(beanName)
將儲存 XML 配置檔案的 GenericBeanDefinition 轉換為 RootBeanDefinition,如果指定 beanName 是子 Bean 的話同時合併父類相關屬性 Textif (mbd.isSingleton()) else if (mbd.isPrototype())
根據屬性 bean 的屬性 scope 進行不同的例項化if (requiredType != null && !requiredType.isInstance(bean))
通過指定需求型別不為空進行型別轉換,否則進行強制轉換
瞭解了建立 bean 的整個過程,其中最重要的就是步驟 8,針對不同的 scope 進行建立。在細化各個步驟提供的功能前,我們先了解一下 FactoryBean 的用法。
FactoryBean 的使用
Spring 通過反射機制來利用 bean 的 class 屬性指定實現類來例項化 bean。在某些情況下,例項 bean 比較複雜,如果在<bean>
標籤中需要大量的配置資訊並且靈活性受限。
Spring 中提供了org.springframework.beans.factory.FactoryBean
的工廠介面,我們可以實現這個介面來定製例項化 bean 的邏輯。
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
/**
* 返回由FactoryBean建立的bean例項,如果isSingleton()返回true,則該例項會放入到Spring容器的單例快取池中
*/
@Nullable
T getObject() throws Exception;
/**
* 返回FactoryBean建立的bean型別
*/
@Nullable
Class<?> getObjectType();
/**
* 返回FactoryBean建立的bean例項的作用域是singleton還是prototype
*/
default boolean isSingleton() {
return true;
}
}
當配置檔案中<bean>
的 class 屬性配置的實現類為 FactoryBean 時,通過getBean()方法獲取返回到的不是 FactoryBean 本身,而是 FactoryBean#getObject()方法所返回的物件,相當於是代理了 getBean()方法。
如果使用傳統方式配置下面 Car 的<bean>
時,每一個屬性則分別對應一個<property>
標籤。
/**
* @author 神祕傑克
* 公眾號: Java菜鳥程式設計師
* @date 2022/5/30
*/
public class Car {
private int maxSpeed;
private String brand;
private double price;
// 省略 get / set
}
如果用 FactoryBean 的方式的話則會更加靈活一點,比如通過逗號分割符的方式一次性為 Car 所有屬性賦值。
/**
* @author 神祕傑克
* 公眾號: Java菜鳥程式設計師
* @date 2022/5/30
*/
public class CarFactoryBean implements FactoryBean<Car> {
private String carInfo;
@Override
public Car getObject() throws Exception {
Car car = new Car();
final String[] infos = carInfo.split(",");
car.setBrand(infos[0]);
car.setMaxSpeed(Integer.parseInt(infos[1]));
car.setPrice(Double.parseDouble(infos[2]));
return car;
}
@Override
public Class<?> getObjectType() {
return Car.class;
}
public String getCarInfo() {
return carInfo;
}
public void setCarInfo(String carInfo) {
this.carInfo = carInfo;
}
@Override
public boolean isSingleton() {
return false;
}
}
有了 CarFactoryBean 後,就可以在配置檔案中使用下面這種自定義配置方式配置 Car Bean 了:
<bean id="car" class="cn.jack.CarFactoryBean">
<property name="carInfo" value="寶馬,400,20000000"/>
</bean>
當呼叫getBean("car")
時,通過反射機制發現了 CarFactoryBean 實現了 FactoryBean 介面,這時候 Spring 容器就呼叫介面方法 CarFactoryBean#getObject()方法返回。如果要獲取 CarFactoryBean 的例項,則需要在 beanName 前面使用“&”作為字首,比如:getBean("&car")
。
快取中獲取單例 bean
瞭解 FactoryBean 的用法後,我們就可以瞭解 bean 載入的過程了。
我們知道單例在 Spring 的同一個容器內只會被建立一次,後續再獲取 bean 直接從單例快取中獲取,注意這裡也只是嘗試載入。
首先嚐試從快取中載入,然後再次嘗試從 singletonFactories 中載入。因為在建立單例 bean 時會存在依賴注入的情況,而在建立依賴的時候為了避免迴圈依賴,Spring 建立 bean 的原則就是不等 bean 建立完成就會將 bean 的 ObjectFactory 提前曝光加入到快取中,一旦下一個 bean 建立時需要依賴上個 bean,則直接使用 ObjectFactory。
public Object getSingleton(String beanName) {
//引數true表示允許早期依賴
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//檢查快取中是否存在例項
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//為空則加鎖
synchronized (this.singletonObjects) {
//如果此時bean正在載入則不處理
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//當某些方法需要提前初始化時候則呼叫addSingleTonFactory方法講對應的ObjectFactory初始化策略存入singletonFactories
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//呼叫預先設定的getObject方法
singletonObject = singletonFactory.getObject();
//記錄到快取中,注意:earlySingletonObjects和singletonFactories是互斥的
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
這個方法首先嚐試從 singletonObjects 中獲取例項,如果獲取不到就從 earlySingletonObjects 中獲取,如果還是獲取不到則從 singletonFactories 裡面獲取 beanName 對應的 ObjectFactory,然後呼叫 ObjectFactory 的 getObject 來建立 bean,並放到 earlySingletonObjects 中,並且從 singletonFactories 中 remove 掉這個 ObjectFactory,後續所有記憶體操作都只為了迴圈依賴檢測時候使用,也就是在 allowEarlyReference 為 true 的情況下使用。
總結
這裡面提到用來儲存 bean 的不同的 map:
singletonObjects
:一級快取,儲存 BeanName 和建立 bean 例項之間的關係,bean name --> bean instanceearlySingletonObjects
:二級快取,儲存 BeanName 和建立 bean 例項之間的關係,與 singletonObjects 不同的是,當一個單例 bean 放入這裡後,那麼當 bean 還在建立過程中時,就可以通過 getBean 方法獲取到了,目的是用來檢測迴圈引用singletonFactories
:三級快取,儲存 BeanName 和建立 bean 的工廠之間的關係,bean name --> ObjectFactoryregisteredSingletons
:該例項是一個 Set 物件,用來儲存當前所有已經註冊的 bean