我在架構(gòu)師面前談 Spring Inner Beans,他直接點(diǎn)頭說:這人有料!
引言
“你聽說了嗎?阿里、字節(jié)最近的Java面試題又加難了!”
“嗯?咋了?”
“Spring又被拿出來問了,這次居然問到了Inner Beans!”
“這不是冷門題嗎?”
“是啊,我一開始還真沒答上來……”
是的!今天要跟大家嘮嗑的,就是這個(gè)在面試中悄悄冒頭,但平時(shí)開發(fā)中卻經(jīng)常被我們忽略的一個(gè)概念:Spring的內(nèi)部Bean(Inner Beans)!
先打個(gè)招呼:大家好呀,我是小米,31歲,Java開發(fā)第9年,一路從小公司寫CRUD寫到十八線大廠做架構(gòu),也面過人也被面過人。最近在整理社招Java高級(jí)崗位的面試題庫(kù),突然看到這道題,心里一驚——這個(gè)概念可真不“顯眼”,卻特別適合考察你對(duì)Spring核心機(jī)制的理解。
于是,我決定,好好寫一篇文章,徹底搞清楚“Spring內(nèi)部Bean”到底是個(gè)啥!
真實(shí)面試場(chǎng)景還原:你知道什么是Inner Beans嗎?
面試官:你用過Spring框架吧?
我:嗯,用了很多年了。
面試官:那你知道Spring中什么是內(nèi)部Bean嗎?
我:……(心中一驚,這玩意我好像見過但沒深入想過?。?/p>
是不是有點(diǎn)耳熟?是不是一臉懵?
其實(shí),我第一次看到Inner Beans,還是在一個(gè) XML 配置文件中,無意間看到某個(gè) <bean> 標(biāo)簽里,嵌套了另一個(gè) <bean> 標(biāo)簽。當(dāng)時(shí)我就懵了:Spring 居然還能這樣寫?
來,咱先用一個(gè)簡(jiǎn)單的例子回憶一下
什么是Spring內(nèi)部Bean(Inner Bean)?
所謂內(nèi)部Bean,其實(shí)就是在 Spring XML 配置中,一個(gè) Bean 的屬性值,是通過直接嵌套另一個(gè) <bean> 元素來定義的。
它的特點(diǎn)是:
只能被包含它的外部 Bean 使用,不能被其他 Bean 引用。
也就是說,這個(gè)內(nèi)部Bean不注冊(cè)到Spring容器的上下文中,沒有id、沒有name,屬于“匿名Bean”。
再直白一點(diǎn):它是一次性、局部使用的Bean,生命周期受限于外部Bean,不會(huì)作為容器中的獨(dú)立Bean。
一個(gè)典型的Inner Bean例子:
圖片
這個(gè)例子中:
- car 是一個(gè)普通Bean,注冊(cè)到容器;
- engine 是一個(gè)內(nèi)部Bean,僅作為 car 的屬性存在;
- engine 沒有id,不能被別的bean引用;
- 它是局部定義的,一次性的。
為什么要用內(nèi)部Bean?它的使用場(chǎng)景有哪些?
當(dāng)我第一次看到這個(gè)語法時(shí),我的第一反應(yīng)是:不如單獨(dú)寫成一個(gè)Bean,起個(gè)名字不是更清晰嗎? 后來深入了解后,我才明白——它其實(shí)有它的用武之地!
使用場(chǎng)景一:依賴關(guān)系簡(jiǎn)單、只使用一次的小組件
如果一個(gè)Bean只被用在一個(gè)地方,比如說Engine只被Car使用,并不會(huì)被其他Bean引用,那把它定義成內(nèi)部Bean能減少命名負(fù)擔(dān)、提高配置清晰度。
就像上面的car和engine例子,engine不會(huì)被其他人用,干嘛要起名字加到Spring容器中呢?
使用場(chǎng)景二:配置簡(jiǎn)潔
對(duì)于那種不太復(fù)雜的項(xiàng)目或Bean結(jié)構(gòu),用內(nèi)部Bean能大大減少配置文件的長(zhǎng)度,不用多寫一堆 <bean> 定義。
使用場(chǎng)景三:匿名類場(chǎng)景(有點(diǎn)像Java里的匿名內(nèi)部類)
這也是我特別喜歡把Inner Bean跟Java的“匿名內(nèi)部類”做對(duì)比的一個(gè)原因。你不需要在別的地方重用它,那就局部定義一下,簡(jiǎn)潔明了!
內(nèi)部Bean的使用限制和坑
雖然聽起來不錯(cuò),但內(nèi)部Bean也不是萬能的,有一些“坑”,我在實(shí)際項(xiàng)目中也遇到過幾次,必須提醒大家!
1) 不能被其他Bean引用
Spring容器并不會(huì)把內(nèi)部Bean注冊(cè)到BeanFactory中,也就是說,你不能通過@Autowired或getBean()拿到它。
如果你定義了一個(gè)內(nèi)部Bean:
你不能這樣引用:
圖片
因?yàn)樗緵]名字!
2) 不能復(fù)用
這其實(shí)跟“不能引用”是一個(gè)意思。你不能在多個(gè)地方復(fù)用這個(gè)Bean,必須每次都重新寫一遍。
3) 不適合有復(fù)雜依賴關(guān)系的Bean
一旦你的內(nèi)部Bean也依賴其他Bean,那配置就會(huì)變得非常難讀。這種情況下,還是建議你把Bean單獨(dú)抽出來定義。
6.Spring注解方式還支持內(nèi)部Bean嗎?
你可能會(huì)問:現(xiàn)在大家都用注解了,@Component, @Bean, @Autowired……還有Inner Bean的說法嗎?
答案是:在注解方式中,Inner Bean 的概念弱化了,但仍然可以實(shí)現(xiàn)類似效果。
比如:
圖片
你看,engine() 是在 car() 方法中被調(diào)用,雖然它有名字,但這個(gè)用法就類似于在定義一個(gè) Bean 時(shí)“內(nèi)部生成了另一個(gè) Bean”。
不過由于方法名就是Bean的名字,它會(huì)注冊(cè)進(jìn)容器。
更接近“內(nèi)部Bean”的用法其實(shí)是:
圖片
這不就是Java代碼版的“Inner Bean”嗎?局部定義、一次性使用、不注冊(cè)到容器、無法注入!
7.我對(duì)Inner Beans的幾點(diǎn)感悟
說實(shí)話,現(xiàn)在的Spring項(xiàng)目大多數(shù)都用注解了,XML配置退居二線。但如果你要進(jìn)大廠、做架構(gòu)、維護(hù)老系統(tǒng),你還真得熟。
Inner Bean這個(gè)概念,看似冷門,卻能考察你:
- 是否理解Spring容器的注冊(cè)機(jī)制;
- 是否了解Bean的作用域;
- 是否有配置簡(jiǎn)潔意識(shí);
- 是否能區(qū)分“局部依賴”與“全局依賴”。
有次我在面一個(gè)10年經(jīng)驗(yàn)的候選人,對(duì)Spring用得很熟,一談到Bean裝配如數(shù)家珍,結(jié)果一問到內(nèi)部Bean,反而一臉茫然??梢娺@是個(gè)很容易被忽略的知識(shí)點(diǎn),但正因?yàn)樗袄溟T”,才更容易拉開差距!
8.答題模板:Spring內(nèi)部Bean面試怎么回答才加分?
如果你遇到了類似的題,可以這樣作答:
Spring的內(nèi)部Bean(Inner Bean)是指在XML配置中作為另一個(gè)Bean屬性值定義的匿名Bean。它只能被包含它的外部Bean使用,不能被容器其他地方引用,不會(huì)注冊(cè)為容器中的獨(dú)立Bean。通常用于只使用一次、依賴簡(jiǎn)單、無需復(fù)用的場(chǎng)景,有助于簡(jiǎn)化配置。雖然在注解配置中這個(gè)概念被弱化,但在維護(hù)老項(xiàng)目時(shí)仍然非常重要。
是不是很清晰?
再來個(gè)加分句:
在注解方式中,也可以通過在方法中直接實(shí)例化對(duì)象的方式實(shí)現(xiàn)“類似”內(nèi)部Bean的效果,但要注意這種方式下對(duì)象并不受Spring容器管理,無法注入和復(fù)用。
9.最后小米的一點(diǎn)私貨建議
對(duì)于準(zhǔn)備社招的伙伴們,如果你目標(biāo)是阿里、字節(jié)、騰訊這些大廠,Spring的邊角料知識(shí)一定要補(bǔ)全!
像Inner Beans、FactoryBean、Scope的作用范圍、Bean的生命周期鉤子方法(init/destroy),甚至循環(huán)依賴的處理機(jī)制,都可能成為一道一道突襲題。
建議你們刷Spring源碼、寫點(diǎn)簡(jiǎn)單的XML配置項(xiàng)目感受一下,然后整理一個(gè)“冷門高頻清單”,每個(gè)知識(shí)點(diǎn)都寫點(diǎn)例子、記錄一下自己的理解,這樣到了面試場(chǎng)上就能有條不紊地答出來。
END
我是小米,一個(gè)愛分享愛學(xué)習(xí)的Java程序員。如果你也正在準(zhǔn)備Java社招,歡迎關(guān)注我,每周都會(huì)更新實(shí)用技術(shù)題庫(kù)、源碼解析、架構(gòu)思維干貨!
也歡迎你在留言區(qū)聊聊:你用過內(nèi)部Bean嗎?你覺得它現(xiàn)在還有用武之地嗎?
讓我們一起,用知識(shí)把面試題變成談笑風(fēng)生的聊天素材。