首頁 > 軟體

Spring Bean生命週期之BeanDefinition的合併過程詳解

2022-03-04 16:00:09

寫在前面

注:本文章使用的 SpringBoot 版本為 2.2.4.RELEASE,其 Spring 版本為 5.2.3.RELEASE

前言

書接上文,BeanDefinition註冊到IoC容器後,緊接著就是要使用Bean了,要使用必須先要獲取Bean,這裡我們就以DefaultListableBeanFactory#getBean方法來引出本次討論的內容:BeanDefinition的合併

通過前面的章節我們瞭解到了BeanDefinition,那什麼是BeanDefinition的合併呢?為什麼要進行合併呢? 帶著這個問題,我們到原始碼中去找找答案。

為了使原始碼邏輯有個參照,這裡先給出一個案例,在分析原始碼時 將這個案例也代入進去方便我們理解原始碼

BeanDefinition的合併原始碼分析

實體類

@Data
public class SuperUser implements Serializable {
    private String address;
    public SuperUser(String address) {
        this.address = address;
    }
    public SuperUser() {
    }
}
@Data
@ToString(callSuper = true)
public class User extends SuperUser {
    private String name;
    private Integer age;
    public User() {
    }
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

基於GenericBeanDefinition註冊有層次的Bean

public class GenericBeanDefinitionDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //父BeanDefinition
        GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();
        rootBeanDefinition.setBeanClass(SuperUser.class);
        //設定引數
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.addPropertyValue("address", "地址");
        rootBeanDefinition.setPropertyValues(propertyValues);
        //子BeanDefinition
        GenericBeanDefinition childBeanDefinition = new GenericBeanDefinition();
        childBeanDefinition.setBeanClass(User.class);
        //設定構造引數
        ConstructorArgumentValues argumentValues = new ConstructorArgumentValues();
        argumentValues.addIndexedArgumentValue(0, "我就是我");
        argumentValues.addIndexedArgumentValue(1, 18);
        childBeanDefinition.setConstructorArgumentValues(argumentValues);
        childBeanDefinition.setParentName("superUser");
        //型別相同時 以子類為主
        childBeanDefinition.setPrimary(true);
        context.registerBeanDefinition("superUser", rootBeanDefinition);
        context.registerBeanDefinition("user", childBeanDefinition);
        context.refresh();
        User user = context.getBean("user", User.class);
        System.out.println(user);
        SuperUser superUser = context.getBean("superUser", SuperUser.class);
        System.out.println(superUser);
        context.close();
    }
}

在分析原始碼時我們要有側重點,這裡會將不太相關的邏輯一帶而過。

AbstractBeanFactory#doGetBean

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
		//將name解析為beanName,如果傳入的是alias,根據aliasMap進行轉換,我們在前面介紹過了
		final String beanName = transformedBeanName(name);
		Object bean;
		// 如果是單例Bean
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			//省略紀錄檔輸出
			// 這裡的邏輯是根據beanName判斷是否為FactoryBea,並採用相應方式去處理
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		//如果不是單例物件
		else {
			// 對原型物件進行驗證,如果當前beanName已經在建立中了 丟擲異常
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}
			// 獲取父BeanFactory,前面我們介紹過了 BeanFactory允許有層級,可已存在父BeanFactory
			BeanFactory parentBeanFactory = getParentBeanFactory();
			//如果存在父BeanFactory 去父BeanFactory中查詢bean
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				//省略去父BeanFactory查詢Bean的過程
			}
			//typeCheckOnly預設為false ,這裡將beanName放到alreadyCreated集合中 表示該Bean正在建立中
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}
			try {
			    // 這裡來到了我們要重點關注的地方了,bd的合併 ⭐️
				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);
				//如果存在依賴Bean,需要進行依賴查詢	
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					// 省略dependsOn 依賴查詢程式碼
				}
				// 這裡的if..else if .. else 是根據scope取值來的
				//scope=singleton時
				if (mbd.isSingleton()) {
					//省略單範例Bean建立過程
				}
				//scope=prototype時
				else if (mbd.isPrototype()) {
					//省略Prototype Bean建立過程
				}
				//scope=request、application、session時
				else {
					// 省略其他Scope Bean的建立過程
		}
		if (requiredType != null && !requiredType.isInstance(bean)) {
			//省略型別轉換程式碼
		}
		// 返回建立的Bean
		return (T) bean;
	}

上面的方法實現比較長、比較複雜,這裡只對重要的地方進行些註釋說明並將與本次討論無關的程式碼先行進行註釋。

下面就進入到BeanDefinition的合併邏輯了

//假設beanName=user
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
		// 檢查快取,若存在直接返回
		RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
		if (mbd != null) {
			return mbd;
		}
    //getBeanDefinition(beanName)==>實際上去DefaultListableBeanFactory.beanDefinitionMap中根據key查詢BeanDefinition,這在註冊階段已經放到beanDefinitionMap了。
		return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
	}
protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd)
			throws BeanDefinitionStoreException {
		return getMergedBeanDefinition(beanName, bd, null);
	}
//根據上面的舉例可知beanName=user,bd是User類的BeanDefinition,containingBd=null
protected RootBeanDefinition getMergedBeanDefinition(
			String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
			throws BeanDefinitionStoreException {
		synchronized (this.mergedBeanDefinitions) {
			RootBeanDefinition mbd = null;
			// 嘗試從快取中拿
			if (containingBd == null) {
				mbd = this.mergedBeanDefinitions.get(beanName);
			}
			if (mbd == null) {
        //如果當前BeanDefinition沒有指定parentName,說明其不存在父BeanDefinition,不需要合併。以RootBeanDefinition形式展現
				if (bd.getParentName() == null) {
					// 如果bd是RootBeanDefinition型別,直接型別轉換
					if (bd instanceof RootBeanDefinition) {
						mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
					}
					else {
            //通過bd屬性構造RootBeanDefinition
						mbd = new RootBeanDefinition(bd);
					}
				}
				else {
					// 走到這裡說明存在parentName,當前bd需要與其父bd合併
					BeanDefinition pbd;
					try {
            //得到父BeanName
						String parentBeanName = transformedBeanName(bd.getParentName());
            //!beanName.equals(parentBeanName) 條件成立 說明當前beanName屬於子bd
						if (!beanName.equals(parentBeanName)) {
              //遞迴地以父bd名稱 查詢父BeanDefinition。之所以遞迴地查詢,是因為 可能此時的parentBeanName還有父,實體類存在多重繼承關係
							pbd = getMergedBeanDefinition(parentBeanName);
						}
						else {
              //走到這裡,說明beanName.equals(parentBeanName),很有可能是父bd查詢BeanDefinition時走來的。
              //獲取父BeanFactory,BeanFactory也是有層次的,有父子關係的,可參見ConfigurableBeanFactory#setParentBeanFactory
							BeanFactory parent = getParentBeanFactory();
							if (parent instanceof ConfigurableBeanFactory) {
								pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
							}
							else {
								throw new NoSuchBeanDefinitionException(parentBeanName,
										"Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
										"': cannot be resolved without an AbstractBeanFactory parent");
							}
						}
					}
					catch (NoSuchBeanDefinitionException ex) {
						throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName,
								"Could not resolve parent bean definition '" + bd.getParentName() + "'", ex);
					}
					// pbd是父BeanDefinition,由其構造為RootBeanDefinition
					mbd = new RootBeanDefinition(pbd);
          //bd是子BeanDefinition,主要是繼承父類別的屬性,並覆蓋與父類別同名的屬性,有興趣的可以看一下overrideFrom方法實現
					mbd.overrideFrom(bd);
				}
				// 如果父bd未指定scope,則設定預設值
				if (!StringUtils.hasLength(mbd.getScope())) {
					mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON);
				}
				//由於containingBd=null 這裡就不看了
				if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
					mbd.setScope(containingBd.getScope());
				}
				if (containingBd == null && isCacheBeanMetadata()) {
					this.mergedBeanDefinitions.put(beanName, mbd);
				}
			}
			//最終返回根據當前beanName找到的bd
			return mbd;
		}
	}

分析了上面的原始碼,我們試著總結一下:

1、如果不存在parentName,即不需要被合併,直接將bd轉為RootBeanDefinition 返回即可

2、如果存在parentName

  • 先根據parentName 找到父bd,若實體存在多級繼承關係,則需要遞迴地查詢。
  • 將父bd轉為RootBeanDefinition,並將子bd與父bd進行合併
  • 設定一些其他屬性

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容! 


IT145.com E-mail:sddin#qq.com