三種Spring BeanName生成器,你了解嗎
1. BeanNameGenerator
Spring 中提供了一個(gè)名為 BeanNameGenerator 的接口,這個(gè)接口就只有一個(gè)需要實(shí)現(xiàn)的方法就是 generateBeanName,從名字就能看出來(lái),這就是專門用來(lái)生成 beanName 的方法。
public?interface?BeanNameGenerator?{ ?String?generateBeanName(BeanDefinition?definition,?BeanDefinitionRegistry?registry); }
這個(gè)方法有兩個(gè)參數(shù):
- definition:這個(gè)是要生成的 Bean 定義。
- registry:這個(gè)是將來(lái) BeanDefinition 的注冊(cè)器。
BeanNameGenerator 有三個(gè)不同的實(shí)現(xiàn)類,對(duì)應(yīng)不同的處理場(chǎng)景:
- AnnotationBeanNameGenerator:這個(gè)專門用來(lái)處理包掃描的時(shí)候掃到的 Bean,對(duì)于這些 Bean,其 name 屬性該如何處理,由這個(gè)類來(lái)解決,當(dāng)然,小伙伴們都知道,通過(guò) @Component/@Service/@Repository/@Controller 這些注解定義的 Bean,默認(rèn)情況下,beanName 就是類名首字母小寫(xiě)。
- FullyQualifiedAnnotationBeanNameGenerator:這個(gè)繼承自 AnnotationBeanNameGenerator,并重寫(xiě)了 AnnotationBeanNameGenerator#buildDefaultBeanName 方法,這個(gè)是使用類的全路徑來(lái)作為 Bean 的默認(rèn)名稱。
- DefaultBeanNameGenerator:這個(gè)是專門用來(lái)解決 XML 文件中定義的 Bean 如果沒(méi)有設(shè)置 beanName,那么就通過(guò) DefaultBeanNameGenerator 來(lái)為其生成 beanName。
看了上面三個(gè)場(chǎng)景之后,可能有小伙伴發(fā)現(xiàn)一個(gè) BUG,那么 @Bean 注解定義的 Bean,其 beanName 屬性是在哪里處理的呢?這個(gè)其實(shí)比較特殊,是當(dāng)場(chǎng)處理的,沒(méi)用到 BeanNameGenerator,松哥后面單獨(dú)說(shuō)。
接下來(lái)我們?cè)敿?xì)看下上面這三個(gè)實(shí)現(xiàn)類。
2. AnnotationBeanNameGenerator
咱們直接來(lái)看最關(guān)鍵的 generateBeanName 方法吧:
@Override public?String?generateBeanName(BeanDefinition?definition,?BeanDefinitionRegistry?registry)?{ ?if?(definition?instanceof?AnnotatedBeanDefinition)?{ ??String?beanName?=?determineBeanNameFromAnnotation((AnnotatedBeanDefinition)?definition); ??if?(StringUtils.hasText(beanName))?{ ???//?Explicit?bean?name?found. ???return?beanName; ??} ?} ?//?Fallback:?generate?a?unique?default?bean?name. ?return?buildDefaultBeanName(definition,?registry); }
這個(gè)方法首先判斷 definition 是否為 AnnotatedBeanDefinition 類型,根據(jù)我們前面文章對(duì) BeanDefinition 的介紹(七種 BeanDefinition,各顯其能!),大家知道,AnnotatedBeanDefinition 的實(shí)現(xiàn)類主要是針對(duì)三種情況:@Bean 注解定義的 Bean、@Service/@Controller/@Component/@Repository 等注解標(biāo)記的 Bean 以及系統(tǒng)的啟動(dòng)配置類,如果是這三種情況,那么就去調(diào)用 determineBeanNameFromAnnotation 方法,這個(gè)方法會(huì)嘗試從注解中提取出來(lái) beanName,如果不是上面三種情況,那么就調(diào)用 buildDefaultBeanName 方法去生成 beanName。
那我們先來(lái)看 determineBeanNameFromAnnotation 方法:
@Nullable protected?String?determineBeanNameFromAnnotation(AnnotatedBeanDefinition?annotatedDef)?{ ?AnnotationMetadata?amd?=?annotatedDef.getMetadata(); ?Set<String>?types?=?amd.getAnnotationTypes(); ?String?beanName?=?null; ?for?(String?type?:?types)?{ ??AnnotationAttributes?attributes?=?AnnotationConfigUtils.attributesFor(amd,?type); ??if?(attributes?!=?null)?{ ???Set<String>?metaTypes?=?this.metaAnnotationTypesCache.computeIfAbsent(type,?key?->?{ ????Set<String>?result?=?amd.getMetaAnnotationTypes(key); ????return?(result.isEmpty()???Collections.emptySet()?:?result); ???}); ???if?(isStereotypeWithNameValue(type,?metaTypes,?attributes))?{ ????Object?value?=?attributes.get("value"); ????if?(value?instanceof?String?strVal)?{ ?????if?(StringUtils.hasLength(strVal))?{ ??????if?(beanName?!=?null?&&?!strVal.equals(beanName))?{ ???????throw?new?IllegalStateException("Stereotype?annotations?suggest?inconsistent?"?+ ?????????"component?names:?'"?+?beanName?+?"'?versus?'"?+?strVal?+?"'"); ??????} ??????beanName?=?strVal; ?????} ????} ???} ??} ?} ?return?beanName; }
這個(gè)方法首先會(huì)去獲取類上的注解信息,拿到 amd 之后,獲取到所有的注解類型,然后進(jìn)行遍歷。
遍歷的時(shí)候,首先獲取到注解上的所有屬性 attributes,當(dāng) attributes 不為空的時(shí)候,繼續(xù)去讀取當(dāng)前注解的元注解,并將讀取到的結(jié)果存入到 metaAnnotationTypesCache 集合中。這個(gè)是干嘛呢?大家知道,Spring 中用來(lái)標(biāo)記 Bean 的注解大部分衍生自 @Component,甚至我們也可以自定義注解,那么如果自定義注解了,這個(gè)地方就沒(méi)法判斷了,因?yàn)槊總€(gè)人自定義出來(lái)的注解都不一樣。所以,萬(wàn)變不離其宗,這里就去找各個(gè)注解的元注解。例如如果我們?cè)陬惿咸砑拥氖?@Configuration,那么 @Configuration 的元注解有兩個(gè),分別是 @Component 和 @Indexed。
接下來(lái)的 isStereotypeWithNameValue 方法就是判斷 type 是不是 @Component 或者 Jakarta 中自帶的 @ManagedBean、@Named,亦或者 metaTypes 里是否包含 @Component。如果確定是 @Component 衍生出來(lái)的注解,亦或者是 @ManagedBean、@Named 注解標(biāo)記的 Bean,那么就將其 value 屬性讀取出來(lái),作為 beanName,如果包含多個(gè)有效注解,且各自配置的 beanName 不一致,就會(huì)拋出異常。
例如下面這種情況:
@Configuration("j") @Component("a") public?class?JavaConfig?{ }
這兩個(gè) beanName 不一致,運(yùn)行時(shí)就會(huì)出錯(cuò)。
同時(shí),經(jīng)過(guò)上面的分析,小伙伴也看到了,我們其實(shí)可以通過(guò)自定義注解為 Bean 設(shè)置名稱,例如我有如下注解:
@Retention(RetentionPolicy.RUNTIME) @Component public?@interface?MyBeanName?{ ????String?value()?default?""; }
這個(gè)注解衍生自 @Component,那么它的用法如下:
@MyBeanName("f") public?class?JavaConfig?{ }
那么 f 就是當(dāng)前類生成的 beanName。
以上是從注解中去提取 beanName,但是注解中可能沒(méi)有提供 beanName,那么就得調(diào)用 buildDefaultBeanName 方法去自動(dòng)生成了,如下:
protected?String?buildDefaultBeanName(BeanDefinition?definition,?BeanDefinitionRegistry?registry)?{ ?return?buildDefaultBeanName(definition); } protected?String?buildDefaultBeanName(BeanDefinition?definition)?{ ?String?beanClassName?=?definition.getBeanClassName(); ?Assert.state(beanClassName?!=?null,?"No?bean?class?name?set"); ?String?shortClassName?=?ClassUtils.getShortName(beanClassName); ?return?StringUtils.uncapitalizeAsProperty(shortClassName); }
這個(gè)就很好懂了,先拿到 bean 的完整類名,然后提取出來(lái) shortName,也就是去除包名之后的名字,然后首字母小寫(xiě)之后返回。
這就是 @Component 注解體系下的 beanName 生成流程。
3. FullyQualifiedAnnotationBeanNameGenerator
FullyQualifiedAnnotationBeanNameGenerator 類只是重寫(xiě)了 AnnotationBeanNameGenerator 的 buildDefaultBeanName 方法,如下:
@Override protected?String?buildDefaultBeanName(BeanDefinition?definition)?{ ?String?beanClassName?=?definition.getBeanClassName(); ?Assert.state(beanClassName?!=?null,?"No?bean?class?name?set"); ?return?beanClassName; }
重寫(xiě)后的方法就是獲取類的完整路徑返回。
FullyQualifiedAnnotationBeanNameGenerator 默認(rèn)情況下并不會(huì)直接使用,需要自己手動(dòng)配置,像下面這樣:
@Configuration @ComponentScan(nameGenerator?=?FullyQualifiedAnnotationBeanNameGenerator.class) public?class?JavaConfig?{ }
此時(shí),生成的 Bean 的默認(rèn)名稱就是類的全路徑了。
4. DefaultBeanNameGenerator
這個(gè)是專門用來(lái)處理 XML 中默認(rèn)的 beanName 的。這個(gè)在最近錄制的 Spring 源碼視頻中已經(jīng)詳細(xì)介紹過(guò)了,這里就不再啰嗦了
5. @Bean 處理特殊情況
如果類是被 @Bean 注解標(biāo)記的,那么處理情況就特殊一些,直接現(xiàn)場(chǎng)處理,方法在 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod
位置:
private?void?loadBeanDefinitionsForBeanMethod(BeanMethod?beanMethod)?{ ?//?Consider?name?and?any?aliases ?List<String>?names?=?new?ArrayList<>(Arrays.asList(bean.getStringArray("name"))); ?String?beanName?=?(!names.isEmpty()???names.remove(0)?:?methodName); ?//?Register?aliases?even?when?overridden ?for?(String?alias?:?names)?{ ??this.registry.registerAlias(beanName,?alias); ?} }
從這里可以看到,如果一開(kāi)始配置了 name 屬性,那么就把 names 集合中的第一個(gè)值拿出來(lái)作為 beanName,集合中的其他值則當(dāng)作別名來(lái)處理,如果沒(méi)有配置 name 屬性值,那么就使用方法名作為 beanName。
到此這篇關(guān)于三種Spring BeanName生成器,你了解嗎的文章就介紹到這了,更多相關(guān)Spring BeanName生成器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java中獲取當(dāng)前服務(wù)器的Ip地址的方法
本篇文章主要介紹了java中獲取當(dāng)前服務(wù)器的Ip地址的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02解決springboot利用ConfigurationProperties注解配置數(shù)據(jù)源無(wú)法讀取配置信息問(wèn)題
今天在學(xué)習(xí)springboot利用ConfigurationProperties注解配置數(shù)據(jù)源的使用遇到一個(gè)問(wèn)題無(wú)法讀取配置信息,發(fā)現(xiàn)全部為null,糾結(jié)是哪里出了問(wèn)題呢,今天一番思考,問(wèn)題根源找到,下面把我的解決方案分享到腳本之家平臺(tái),感興趣的朋友一起看看吧2021-05-05使用Log4j2代碼方式配置實(shí)現(xiàn)線程級(jí)動(dòng)態(tài)控制
這篇文章主要介紹了使用Log4j2代碼方式配置實(shí)現(xiàn)線程級(jí)動(dòng)態(tài)控制,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Java位集合之BitMap、BitSet和布隆過(guò)濾器示例解析
這篇文章主要介紹了Java中位集合的基本概念、實(shí)現(xiàn)方法以及應(yīng)用場(chǎng)景,包括Bit-Map、BitSet和BloomFilter,Bit-Map通過(guò)位操作高效地存儲(chǔ)和查詢?cè)貭顟B(tài),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-12-12Spring?Boot多數(shù)據(jù)源事務(wù)@DSTransactional的使用詳解
本文主要介紹了Spring?Boot多數(shù)據(jù)源事務(wù)@DSTransactional的使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06Spring?Security自定義登錄頁(yè)面認(rèn)證過(guò)程常用配置
這篇文章主要為大家介紹了Spring?Security自定義登錄頁(yè)面認(rèn)證過(guò)程常用配置示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08Java的MyBatis+Spring框架中使用數(shù)據(jù)訪問(wèn)對(duì)象DAO模式的方法
Data Access Object數(shù)據(jù)訪問(wèn)對(duì)象模式在Java操作數(shù)據(jù)庫(kù)部分的程序設(shè)計(jì)中經(jīng)常被使用到,這里我們就來(lái)看一下Java的MyBatis+Spring框架中使用數(shù)據(jù)訪問(wèn)對(duì)象DAO模式的方法:2016-06-06使用list stream:對(duì)List中的對(duì)象先進(jìn)行排序再獲取前n個(gè)對(duì)象
這篇文章主要介紹了使用list stream:對(duì)List中的對(duì)象先進(jìn)行排序再獲取前n個(gè)對(duì)象,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09