Spring?BeanDefinition父子關系示例解析
引言
在 Spring 框架中,BeanDefinition 是一個核心概念,用于定義和配置 bean 的元數(shù)據(jù),雖然在實際應用中,我們一般并不會或者很少直接定義 BeanDefinition,但是,我們在 XML 文件中所作的配置,以及利用 Java 代碼做的各種 Spring 配置,都會被解析為 BeanDefinition,然后才會做進一步的處理。BeanDefinition 允許開發(fā)人員以一種聲明性的方式定義和組織 bean,這里有很多屬性,今天松哥單純的來和小伙伴們聊一聊它的 parentName 屬性,parentName 屬性在 BeanDefinition 中扮演著重要的角色,用于建立 bean 之間的父子關系。
之前有一篇文章和小伙伴們聊了 BeanFactory 之間的父子關系(Spring 中的父子容器是咋回事?),大家注意和今天的內(nèi)容進行區(qū)分,今天我們聊的是 BeanDefinition 之間的父子關系。
BeanDefinition 的 parentName 屬性的主要功能是允許我們在創(chuàng)建一個 bean 的同時,能夠繼承另一個已經(jīng)定義好的 bean。通過指定 parentName 屬性,我們可以重用已有 bean 的配置,并在此基礎上進行修改或擴展。
先不廢話了,我先來舉兩個例子,小伙伴們先感受一下 BeanDefinition 的作用。
1. 實踐
假設我有如下兩個類,首先是一個動物的基類,如下:
public class Animal { private String name; private Integer age; //省略 getter/setter }
然后有一個 Dog 類,如下:
public class Dog { private String name; private Integer age; private String color; //省略 getter/setter }
小伙伴們注意,這里的 Dog 類并沒有繼承自 Animal 類,但是有兩個跟 Animal 同名的屬性。之所以這樣設計是希望小伙伴們理解 BeanDefinition 中的 parentName 屬性和 Java 中的繼承并無關系,雖然大部分情況下我們用到 parentName 的時候,Java 中相關的類都是繼承關系。
現(xiàn)在,有一些通用的屬性我想在 Animal 中進行配置,Dog 中特有的屬性則在 Dog 中進行配置,我們來看下通過 XML 和 Java 分別該如何配置。
1.1 XML 配置
<bean id="animal" class="org.javaboy.demo.p2.Animal"> <property name="name" value="小黑"/> <property name="age" value="3"/> </bean> <bean class="org.javaboy.demo.p2.Dog" id="dog" parent="animal"> <property name="color" value="黑色"/> </bean>
小伙伴們看到,首先我們配置 Animal,Animal 中有 name 和 age 兩個屬性,然后我又配置了 Dog Bean,并未之指定了 parent 為 animal,然后給 Dog 設置了 color 屬性。
現(xiàn)在,Dog Bean 定義出來的 BeanDefinition 中將來就包含了 animal 中的屬性值。
1.2 Java 配置
再來看看 Java 配置該如何寫。
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); RootBeanDefinition pbd = new RootBeanDefinition(); MutablePropertyValues pValues = new MutablePropertyValues(); pValues.add("name", "小黃"); pbd.setBeanClass(Animal.class); pbd.setPropertyValues(pValues); GenericBeanDefinition cbd = new GenericBeanDefinition(); cbd.setBeanClass(Dog.class); cbd.setParentName("parent"); MutablePropertyValues cValues = new MutablePropertyValues(); cValues.add("name", "小強"); cbd.setPropertyValues(cValues); ctx.registerBeanDefinition("parent", pbd); ctx.registerBeanDefinition("child", cbd); ctx.refresh(); Dog child = (Dog) ctx.getBean("child"); System.out.println("child = " + child);
這里我使用了 RootBeanDefinition 來做 parent,其實從名字上就能看出來 RootBeanDefinition 適合做 parent,并且 RootBeanDefinition 不能作為 child。強行設置運行時會拋出異常,RootBeanDefinition#setParentName 方法如下:
@Override public void setParentName(@Nullable String parentName) { if (parentName != null) { throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference"); } }
MutablePropertyValues 是為相應的對象設置屬性值。
child 我這里使用了 GenericBeanDefinition,這個主要是做 child 的處理,最早有一個專門做 child 的 ChildBeanDefinition,不過自從 Spring2.5 開始提供了 GenericBeanDefinition 之后,現(xiàn)在用來做 child 首選 GenericBeanDefinition。
在上述案例中,parent 和 child 都設置了 name 屬性,那么 child 會覆蓋掉 parent,這一點和 Java 中的繼承一致。
用法就是這樣,并不難。
這就是 Spring BeanDefinition 中的父子關系問題。
2. 源碼分析
那么接下來我們也把這塊的源碼稍微來分析一下。
簡便起見,我們就不從 Bean 的創(chuàng)建開始分析了,直接來看和 BeanDefinition 中 parentName 屬性相關的地方,但是前面涉及到的方法還是給小伙伴們梳理一下,就是下圖:
那么這里涉及到的關鍵方法其實就是 AbstractBeanFactory#getMergedBeanDefinition:
protected RootBeanDefinition getMergedBeanDefinition( String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) throws BeanDefinitionStoreException { synchronized (this.mergedBeanDefinitions) { RootBeanDefinition mbd = null; RootBeanDefinition previous = null; // Check with full lock now in order to enforce the same merged instance. if (containingBd == null) { mbd = this.mergedBeanDefinitions.get(beanName); } if (mbd == null || mbd.stale) { previous = mbd; if (bd.getParentName() == null) { // Use copy of given root bean definition. if (bd instanceof RootBeanDefinition rootBeanDef) { mbd = rootBeanDef.cloneBeanDefinition(); } else { mbd = new RootBeanDefinition(bd); } } else { // Child bean definition: needs to be merged with parent. BeanDefinition pbd; try { String parentBeanName = transformedBeanName(bd.getParentName()); if (!beanName.equals(parentBeanName)) { pbd = getMergedBeanDefinition(parentBeanName); } else { if (getParentBeanFactory() instanceof ConfigurableBeanFactory parent) { pbd = parent.getMergedBeanDefinition(parentBeanName); } else { throw new NoSuchBeanDefinitionException(parentBeanName, "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName + "': cannot be resolved without a ConfigurableBeanFactory parent"); } } } // Deep copy with overridden values. mbd = new RootBeanDefinition(pbd); mbd.overrideFrom(bd); } // Set default singleton scope, if not configured before. if (!StringUtils.hasLength(mbd.getScope())) { mbd.setScope(SCOPE_SINGLETON); } // A bean contained in a non-singleton bean cannot be a singleton itself. // Let's correct this on the fly here, since this might be the result of // parent-child merging for the outer bean, in which case the original inner bean // definition will not have inherited the merged outer bean's singleton status. if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) { mbd.setScope(containingBd.getScope()); } // Cache the merged bean definition for the time being // (it might still get re-merged later on in order to pick up metadata changes) if (containingBd == null && isCacheBeanMetadata()) { this.mergedBeanDefinitions.put(beanName, mbd); } } if (previous != null) { copyRelevantMergedBeanDefinitionCaches(previous, mbd); } return mbd; } }
這個方法看名字就是要獲取一個合并之后的 BeanDefinition,就是將 child 中的屬性和 parent 中的屬性進行合并,然后返回,這個方法中有一個名為 mbd 的變量,這就是合并之后的結果。
- 首先會嘗試從 mergedBeanDefinitions 變量中獲取到合并之后的 BeanDefinition,mergedBeanDefinitions 相當于就是一個臨時緩存,如果之前已經(jīng)獲取過了,那么獲取成功之后就將之保存到 mergedBeanDefinitions 中,如果是第一次進入到該方法中,那么該變量中就沒有我們需要的數(shù)據(jù),所以會繼續(xù)執(zhí)行后面的步驟。
- 當?shù)?1 步并未拿到 mbd 的時候,接下來繼續(xù)判斷
bd.getParentName()
是否為空,這個其實就是查看當前的 BeanDefinition 是否有設置 parentName,如果有設置,這里獲取到的就不為 null,否則為 null。如果這里獲取到的值為 null,那么就會根據(jù)當前傳入的 BeanDefinition 生成一個 mbd,至于具體的生成方式:如果傳入的 BeanDefinition 是 RootBeanDefinition 類型的,則調(diào)用 clone 方法去生成 mbd(本質(zhì)上也是 new 一個新的 RootBeanDefinition),如果傳入的 BeanDefinition 不是 RootBeanDefinition 類型的,則直接 new 一個新的 RootBeanDefinition,在 new 的過程中,會把傳入的 BeanDefinition 上的屬性都復制到新的 RootBeanDefinition 中。 - 如果
bd.getParentName()
不為空,則意味著存在 parent BeanDefinition,所以就要進行合并處理了,合并時候又有一個小細節(jié),如果 parentBeanName 等于當前的 beanName,由于 Spring 在同一個容器中不允許存在同名的 bean,所以這就說明 parentBeanName 可能是父容器的 Bean,此時就要去父容器中去處理,當然最終調(diào)用到的還是當前方法,關于父子容器這一塊,小伙伴們可以參考之前的 Spring 中的父子容器是咋回事? 一文。如果 parentBeanName 不等于當前 beanName,那么現(xiàn)在就可以調(diào)用 getMergedBeanDefinition 方法去獲取到 parentBeanDefinition 了,getMergedBeanDefinition 是當前方法的重載方法,該方法最終也會調(diào)用到當前方法,原因就在于 parentBeanDefinition 本身也可能存在 parentBeanDefinition。 - 有了 pbd 之后,接下來 new 一個 RootBeanDefinition,然后調(diào)用 overrideFrom 方法進行屬性合并,合并的方式就是用傳入的 BeanDefinition 中的屬性去覆蓋 pbd 中同名的屬性。
- 最后就是再設置 scope 屬性等,然后把 mbd 返回即可。
核心流程就是上面這個步驟,如此之后,拿到手的就是和 parent 合并之后的 BeanDefinition 了。
3. 小結
最后我們再來稍微總結下:
使用 parentName 屬性的一個主要優(yōu)勢是提高代碼的可維護性和重用性。當我們需要創(chuàng)建多個相似的 bean 時,可以通過定義一個基礎 bean,并在其他 bean 中使用 parentName 屬性來繼承其配置。這樣,我們只需在基礎 bean 中定義一次配置,而不必為每個派生 bean 重復相同的配置。
另一個使用 parentName 屬性的場景是在多個層次結構中定義 bean。假設我們有一個通用的基礎服務層 bean,而不同的業(yè)務模塊需要在此基礎上進行擴展。通過使用 parentName 屬性,我們可以為每個業(yè)務模塊定義一個派生 bean,并在其中添加特定于模塊的配置。這種層次結構的定義使得我們可以更好地組織和管理不同模塊之間的 bean。
通過使用 parentName 屬性,我們可以輕松地創(chuàng)建和管理 bean 的層次結構。這種繼承關系使得我們可以更好地組織和重用 bean 的配置,減少了代碼的冗余性。同時,它還提供了一種靈活的方式來定義不同模塊之間的 bean,使得應用程序更易于擴展和維護。
綜上所述,Spring 框架中的 BeanDefinition 的 parentName 屬性允許我們在定義 bean 時建立父子關系,從而提高代碼的可維護性和重用性。通過繼承已有 bean 的配置,我們可以避免重復編寫相似的配置,并更好地組織和管理不同層次結構的 bean。
有的小伙伴們可能會搞混今天內(nèi)容和之前松哥所寫的 Spring 父子容器之間的關系,小伙伴們參考這篇文章就清楚啦:Spring 中的父子容器是咋回事?。
另外,Spring BeanDefinition 中的 parentName 和 Java 中的繼承雖然有點像,但是并不能同等看待,它們之間也還是有一些區(qū)別的:
- 概念和作用:Java 中的繼承是一種面向?qū)ο蟮木幊谈拍?,用于定義類之間的父子關系,子類繼承父類的屬性和方法。而在 Spring 中,BeanDefinition 的 parentName 屬性是用于定義 bean 之間的父子關系,一個派生 bean 可以繼承另一個已定義的 bean 的配置。
- 語法和用法:在 Java 中,繼承是通過使用關鍵字
extends
來實現(xiàn)的,子類通過繼承父類來獲得父類的屬性和方法。而在 Spring 中,通過在 BeanDefinition 中配置 parentName 屬性來指定一個 bean 的父 bean,從而繼承父 bean 的配置。 - 范圍和應用:Java 中的繼承主要用于類的繼承關系,用于定義類之間的層次結構和代碼的重用。而在 Spring 中,BeanDefinition 的繼承主要用于定義 bean 之間的配置繼承關系,用于組織和管理 bean 的配置,提高代碼的可維護性和重用性。
好啦,Spring BeanDefinition 中的 parentName 屬性現(xiàn)在大家明白了吧~
以上就是Spring BeanDefinition父子關系示例解析的詳細內(nèi)容,更多關于Spring BeanDefinition父子關系的資料請關注腳本之家其它相關文章!
相關文章
通過實例了解Java Integer類和int的區(qū)別
這篇文章主要介紹了通過實例了解Java Integer類和int的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-03-03mybatis?plus?MetaObjectHandler?不生效的解決
今天使用mybatis-plus自動為更新和插入操作插入更新時間和插入時間,配置了MetaObjectHandler不生效,本文就來解決一下,具有一定的 參考價值,感興趣的可以了解一下2023-10-10使用Okhttp實現(xiàn)上傳文件+參數(shù)請求接口form-data
在進行接口對接時,常遇到需要傳遞多種類型參數(shù)及文件上傳的情況,解決此問題的關鍵在于參數(shù)傳遞和文件上傳的正確處理,在Service層和Controller層的傳參,可以通過@RequestParam標注或直接使用請求實體類,但若結合文件上傳,則不應使用@RequestBody注解2024-10-10Java class文件格式之方法_動力節(jié)點Java學院整理
這篇文章主要為大家詳細介紹了Java class文件格式之方法的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06SpringBoot+Redis使用AOP防止重復提交的實現(xiàn)
本文主要介紹了SpringBoot+Redis使用AOP防止重復提交的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07