Spring?BeanDefinition父子關(guān)系示例解析
引言
在 Spring 框架中,BeanDefinition 是一個核心概念,用于定義和配置 bean 的元數(shù)據(jù),雖然在實際應(yīng)用中,我們一般并不會或者很少直接定義 BeanDefinition,但是,我們在 XML 文件中所作的配置,以及利用 Java 代碼做的各種 Spring 配置,都會被解析為 BeanDefinition,然后才會做進一步的處理。BeanDefinition 允許開發(fā)人員以一種聲明性的方式定義和組織 bean,這里有很多屬性,今天松哥單純的來和小伙伴們聊一聊它的 parentName 屬性,parentName 屬性在 BeanDefinition 中扮演著重要的角色,用于建立 bean 之間的父子關(guān)系。
之前有一篇文章和小伙伴們聊了 BeanFactory 之間的父子關(guān)系(Spring 中的父子容器是咋回事?),大家注意和今天的內(nèi)容進行區(qū)分,今天我們聊的是 BeanDefinition 之間的父子關(guān)系。
BeanDefinition 的 parentName 屬性的主要功能是允許我們在創(chuàng)建一個 bean 的同時,能夠繼承另一個已經(jīng)定義好的 bean。通過指定 parentName 屬性,我們可以重用已有 bean 的配置,并在此基礎(chǔ)上進行修改或擴展。
先不廢話了,我先來舉兩個例子,小伙伴們先感受一下 BeanDefinition 的作用。
1. 實踐
假設(shè)我有如下兩個類,首先是一個動物的基類,如下:
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 同名的屬性。之所以這樣設(shè)計是希望小伙伴們理解 BeanDefinition 中的 parentName 屬性和 Java 中的繼承并無關(guān)系,雖然大部分情況下我們用到 parentName 的時候,Java 中相關(guān)的類都是繼承關(guān)系。
現(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 設(shè)置了 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。強行設(shè)置運行時會拋出異常,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 是為相應(yīng)的對象設(shè)置屬性值。
child 我這里使用了 GenericBeanDefinition,這個主要是做 child 的處理,最早有一個專門做 child 的 ChildBeanDefinition,不過自從 Spring2.5 開始提供了 GenericBeanDefinition 之后,現(xiàn)在用來做 child 首選 GenericBeanDefinition。
在上述案例中,parent 和 child 都設(shè)置了 name 屬性,那么 child 會覆蓋掉 parent,這一點和 Java 中的繼承一致。
用法就是這樣,并不難。
這就是 Spring BeanDefinition 中的父子關(guān)系問題。
2. 源碼分析
那么接下來我們也把這塊的源碼稍微來分析一下。
簡便起見,我們就不從 Bean 的創(chuàng)建開始分析了,直接來看和 BeanDefinition 中 parentName 屬性相關(guān)的地方,但是前面涉及到的方法還是給小伙伴們梳理一下,就是下圖:
那么這里涉及到的關(guān)鍵方法其實就是 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 的變量,這就是合并之后的結(jié)果。
- 首先會嘗試從 mergedBeanDefinitions 變量中獲取到合并之后的 BeanDefinition,mergedBeanDefinitions 相當(dāng)于就是一個臨時緩存,如果之前已經(jīng)獲取過了,那么獲取成功之后就將之保存到 mergedBeanDefinitions 中,如果是第一次進入到該方法中,那么該變量中就沒有我們需要的數(shù)據(jù),所以會繼續(xù)執(zhí)行后面的步驟。
- 當(dāng)?shù)?1 步并未拿到 mbd 的時候,接下來繼續(xù)判斷
bd.getParentName()
是否為空,這個其實就是查看當(dāng)前的 BeanDefinition 是否有設(shè)置 parentName,如果有設(shè)置,這里獲取到的就不為 null,否則為 null。如果這里獲取到的值為 null,那么就會根據(jù)當(dāng)前傳入的 BeanDefinition 生成一個 mbd,至于具體的生成方式:如果傳入的 BeanDefinition 是 RootBeanDefinition 類型的,則調(diào)用 clone 方法去生成 mbd(本質(zhì)上也是 new 一個新的 RootBeanDefinition),如果傳入的 BeanDefinition 不是 RootBeanDefinition 類型的,則直接 new 一個新的 RootBeanDefinition,在 new 的過程中,會把傳入的 BeanDefinition 上的屬性都復(fù)制到新的 RootBeanDefinition 中。 - 如果
bd.getParentName()
不為空,則意味著存在 parent BeanDefinition,所以就要進行合并處理了,合并時候又有一個小細節(jié),如果 parentBeanName 等于當(dāng)前的 beanName,由于 Spring 在同一個容器中不允許存在同名的 bean,所以這就說明 parentBeanName 可能是父容器的 Bean,此時就要去父容器中去處理,當(dāng)然最終調(diào)用到的還是當(dāng)前方法,關(guān)于父子容器這一塊,小伙伴們可以參考之前的 Spring 中的父子容器是咋回事? 一文。如果 parentBeanName 不等于當(dāng)前 beanName,那么現(xiàn)在就可以調(diào)用 getMergedBeanDefinition 方法去獲取到 parentBeanDefinition 了,getMergedBeanDefinition 是當(dāng)前方法的重載方法,該方法最終也會調(diào)用到當(dāng)前方法,原因就在于 parentBeanDefinition 本身也可能存在 parentBeanDefinition。 - 有了 pbd 之后,接下來 new 一個 RootBeanDefinition,然后調(diào)用 overrideFrom 方法進行屬性合并,合并的方式就是用傳入的 BeanDefinition 中的屬性去覆蓋 pbd 中同名的屬性。
- 最后就是再設(shè)置 scope 屬性等,然后把 mbd 返回即可。
核心流程就是上面這個步驟,如此之后,拿到手的就是和 parent 合并之后的 BeanDefinition 了。
3. 小結(jié)
最后我們再來稍微總結(jié)下:
使用 parentName 屬性的一個主要優(yōu)勢是提高代碼的可維護性和重用性。當(dāng)我們需要創(chuàng)建多個相似的 bean 時,可以通過定義一個基礎(chǔ) bean,并在其他 bean 中使用 parentName 屬性來繼承其配置。這樣,我們只需在基礎(chǔ) bean 中定義一次配置,而不必為每個派生 bean 重復(fù)相同的配置。
另一個使用 parentName 屬性的場景是在多個層次結(jié)構(gòu)中定義 bean。假設(shè)我們有一個通用的基礎(chǔ)服務(wù)層 bean,而不同的業(yè)務(wù)模塊需要在此基礎(chǔ)上進行擴展。通過使用 parentName 屬性,我們可以為每個業(yè)務(wù)模塊定義一個派生 bean,并在其中添加特定于模塊的配置。這種層次結(jié)構(gòu)的定義使得我們可以更好地組織和管理不同模塊之間的 bean。
通過使用 parentName 屬性,我們可以輕松地創(chuàng)建和管理 bean 的層次結(jié)構(gòu)。這種繼承關(guān)系使得我們可以更好地組織和重用 bean 的配置,減少了代碼的冗余性。同時,它還提供了一種靈活的方式來定義不同模塊之間的 bean,使得應(yīng)用程序更易于擴展和維護。
綜上所述,Spring 框架中的 BeanDefinition 的 parentName 屬性允許我們在定義 bean 時建立父子關(guān)系,從而提高代碼的可維護性和重用性。通過繼承已有 bean 的配置,我們可以避免重復(fù)編寫相似的配置,并更好地組織和管理不同層次結(jié)構(gòu)的 bean。
有的小伙伴們可能會搞混今天內(nèi)容和之前松哥所寫的 Spring 父子容器之間的關(guān)系,小伙伴們參考這篇文章就清楚啦:Spring 中的父子容器是咋回事?。
另外,Spring BeanDefinition 中的 parentName 和 Java 中的繼承雖然有點像,但是并不能同等看待,它們之間也還是有一些區(qū)別的:
- 概念和作用:Java 中的繼承是一種面向?qū)ο蟮木幊谈拍睿糜诙x類之間的父子關(guān)系,子類繼承父類的屬性和方法。而在 Spring 中,BeanDefinition 的 parentName 屬性是用于定義 bean 之間的父子關(guān)系,一個派生 bean 可以繼承另一個已定義的 bean 的配置。
- 語法和用法:在 Java 中,繼承是通過使用關(guān)鍵字
extends
來實現(xiàn)的,子類通過繼承父類來獲得父類的屬性和方法。而在 Spring 中,通過在 BeanDefinition 中配置 parentName 屬性來指定一個 bean 的父 bean,從而繼承父 bean 的配置。 - 范圍和應(yīng)用:Java 中的繼承主要用于類的繼承關(guān)系,用于定義類之間的層次結(jié)構(gòu)和代碼的重用。而在 Spring 中,BeanDefinition 的繼承主要用于定義 bean 之間的配置繼承關(guān)系,用于組織和管理 bean 的配置,提高代碼的可維護性和重用性。
好啦,Spring BeanDefinition 中的 parentName 屬性現(xiàn)在大家明白了吧~
以上就是Spring BeanDefinition父子關(guān)系示例解析的詳細內(nèi)容,更多關(guān)于Spring BeanDefinition父子關(guān)系的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
通過實例了解Java Integer類和int的區(qū)別
這篇文章主要介紹了通過實例了解Java Integer類和int的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03mybatis?plus?MetaObjectHandler?不生效的解決
今天使用mybatis-plus自動為更新和插入操作插入更新時間和插入時間,配置了MetaObjectHandler不生效,本文就來解決一下,具有一定的 參考價值,感興趣的可以了解一下2023-10-10Java游戲服務(wù)器系列之Netty相關(guān)知識總結(jié)
今天帶大家來學(xué)習(xí)Java游戲服務(wù)器的相關(guān)知識,文中對Netty作了非常詳細的介紹,對正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下2021-05-05使用Okhttp實現(xiàn)上傳文件+參數(shù)請求接口form-data
在進行接口對接時,常遇到需要傳遞多種類型參數(shù)及文件上傳的情況,解決此問題的關(guān)鍵在于參數(shù)傳遞和文件上傳的正確處理,在Service層和Controller層的傳參,可以通過@RequestParam標注或直接使用請求實體類,但若結(jié)合文件上傳,則不應(yīng)使用@RequestBody注解2024-10-10Java class文件格式之方法_動力節(jié)點Java學(xué)院整理
這篇文章主要為大家詳細介紹了Java class文件格式之方法的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06SpringBoot+Redis使用AOP防止重復(fù)提交的實現(xiàn)
本文主要介紹了SpringBoot+Redis使用AOP防止重復(fù)提交的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07