2023-07-12 09:22:38來源:江南一點(diǎn)雨
相信有小伙伴也聽說過,在 SSM 項(xiàng)目中,Spring 容器是父容器,SpringMVC 是子容器,子容器可以訪問父容器的 Bean,但是父容器不能訪問子容器的 Bean。
(資料圖片僅供參考)
更近一步,有小伙伴可能也了解過,不用父子容器,單純就用一個(gè) SpringMVC 容器似乎也可以,項(xiàng)目也能運(yùn)行。
那么現(xiàn)在問題來了:既然單純一個(gè) SpringMVC 容器就能使項(xiàng)目跑起來,那我們?yōu)槭裁催€要用父子容器?父子容器的優(yōu)勢(shì)是什么?
帶著這個(gè)問題,今天松哥來和小伙伴們聊一聊父子容器。
1. 父子容器首先,其實(shí)父子這種設(shè)計(jì)很常見,松哥記得在之前的 Spring Security 的系列文章中,Spring Security 中的 AuthenticationManager 其實(shí)也是類似的設(shè)計(jì),估計(jì)那里就是借鑒了 Spring 中的父子容器設(shè)計(jì)。
當(dāng)使用了父子容器之后,如果去父容器中查找 Bean,那么就單純的在父容器中查找 Bean;如果是去子容器中查找 Bean,那么就會(huì)先在子容器中查找,找到了就返回,沒找到則繼續(xù)去父容器中查找,直到找到為止(把父容器都找完了還是沒有的話,那就只能拋異常出來了)。
2. 為什么需要父子容器2.1 問題呈現(xiàn)為什么需要父子容器?老老實(shí)實(shí)使用一個(gè)容器不行嗎?
既然 Spring 容器中有父子容器,那么這個(gè)玩意就必然有其使用場(chǎng)景。
松哥舉一個(gè)簡(jiǎn)單的例子。
假設(shè)我有一個(gè)多模塊項(xiàng)目,其中有商家模塊和客戶模塊,商家模塊和客戶模塊中都有角色管理 RoleService,項(xiàng)目結(jié)構(gòu)如下圖:
├── admin│ ├── pom.xml│ └── src│ ├── main│ │ ├── java│ │ └── resources├── consumer│ ├── pom.xml│ └── src│ ├── main│ │ ├── java│ │ │ └── org│ │ │ └── javaboy│ │ │ └── consumer│ │ │ └── RoleService.java│ │ └── resources│ │ └── consumer_beans.xml├── merchant│ ├── pom.xml│ └── src│ ├── main│ │ ├── java│ │ │ └── org│ │ │ └── javaboy│ │ │ └── merchant│ │ │ └── RoleService.java│ │ └── resources│ │ └── merchant_beans.xml└── pom.xml
現(xiàn)在 consumer 和 merchant 中都有一個(gè) RoleService 類,然后在各自的配置文件中,都將該類注冊(cè)到 Spring 容器中。
org.javaboy.consumer.RoleService:
public class RoleService { public String hello() { return "hello consumer"; }}
org.javaboy.merchant.RoleService:
public class RoleService { public String hello() { return "hello merchant"; }}
consumer_beans.xml 如下:
merchant_beans.xml 如下:
大家注意,這兩個(gè) Bean 同名。
現(xiàn)在,在 admin 模塊中,同時(shí)依賴 consumer 和 merchant,同時(shí)加載這兩個(gè)配置文件,那么能不能同時(shí)向 Spring 容器中注冊(cè)兩個(gè)來自不同模塊的同名 Bean 呢?
代碼如下:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();ctx.setConfigLocations("consumer_beans.xml", "merchant_beans.xml");ctx.refresh();org.javaboy.merchant.RoleService rs1 = ctx.getBean(org.javaboy.merchant.RoleService.class);org.javaboy.consumer.RoleService rs2 = ctx.getBean(org.javaboy.consumer.RoleService.class);
這個(gè)執(zhí)行之后會(huì)拋出如下問題:
小伙伴們看到,這個(gè)是找不到org.javaboy.consumer.RoleService服務(wù),但是另外一個(gè) RoleService 其實(shí)是找到了,因?yàn)槟J(rèn)情況下后面定義的同名 Bean 把前面的覆蓋了,所以有一個(gè) Bean 就找不到了。
如果不允許 Bean 的覆蓋,那么可以進(jìn)行如下配置:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();ctx.setConfigLocations("consumer_beans.xml", "merchant_beans.xml");ctx.setAllowBeanDefinitionOverriding(false);ctx.refresh();
此時(shí)一啟動(dòng)就直接報(bào)錯(cuò)了:
圖片
意思也說的比較明確了,Bean 的定義沖突了,所以定義失敗。
那么有沒有辦法能夠優(yōu)雅的解決上面這個(gè)問題呢?答案就是父子容器!
2.2 父子容器對(duì)于上面的問題,我們可以將 consumer 和 merchant 配置成父子關(guān)系或者兄弟關(guān)系,就能很好的解決這個(gè)問題了。
2.2.1 兄弟關(guān)系先來看兄弟關(guān)系,代碼如下:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();ClassPathXmlApplicationContext child1 = new ClassPathXmlApplicationContext("consumer_beans.xml");ClassPathXmlApplicationContext child2 = new ClassPathXmlApplicationContext("merchant_beans.xml");child1.setParent(ctx);child2.setParent(ctx);ctx.setAllowBeanDefinitionOverriding(false);ctx.refresh();org.javaboy.consumer.RoleService rs1 = child1.getBean(org.javaboy.consumer.RoleService.class);org.javaboy.merchant.RoleService rs2 = child2.getBean(org.javaboy.merchant.RoleService.class);System.out.println("rs1.hello() = " + rs1.hello());System.out.println("rs2.hello() = " + rs2.hello());
小伙伴們看一下,這種針對(duì) consumer 和 merchant 分別創(chuàng)建了容器,這種容器關(guān)系就是兄弟容器,這兩個(gè)兄弟有一個(gè)共同的 parent 就是 ctx,現(xiàn)在可以在各個(gè)容器中獲取到自己的 Bean 了。
需要注意的是,上面這種結(jié)構(gòu)中,子容器可以獲取到 parent 的 Bean,但是無法獲取到兄弟容器的 Bean,即如果 consumer 中引用了 merchant 中的 Bean,那么上面這個(gè)配置就有問題了。
2.2.2 父子關(guān)系現(xiàn)在假設(shè)用 consumer 做 parent 容器,merchant 做 child 容器,那么配置如下:
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext("merchant_beans.xml");child.setParent(parent);child.refresh();org.javaboy.consumer.RoleService rs1 = parent.getBean(org.javaboy.consumer.RoleService.class);org.javaboy.merchant.RoleService rs2 = child.getBean(org.javaboy.merchant.RoleService.class);org.javaboy.consumer.RoleService rs3 = child.getBean(org.javaboy.consumer.RoleService.class);System.out.println("rs1.hello() = " + rs1.hello());System.out.println("rs2.hello() = " + rs2.hello());System.out.println("rs3.hello() = " + rs3.hello());
首先創(chuàng)建兩個(gè)容器,分別是 parent 和 child,然后為 child 容器設(shè)置 parent,設(shè)置完成后記得要刷新 child 容器。
現(xiàn)在我們就可以從 parent 容器中去獲取 parent 容器中原本就存在的 Bean,也可以從 child 容器中去獲取 child 容器原本的 Bean 或者是 parent 的 Bean 都可以。
這就是父子容器。
父容器和子容器本質(zhì)上是相互隔離的兩個(gè)不同的容器,所以允許同名的 Bean 存在。當(dāng)子容器調(diào)用 getBean 方法去獲取一個(gè) Bean 的時(shí)候,如果當(dāng)前容器沒找到,就會(huì)去父容器查找,一直往上找,找到為止。
核心就是 BeanFactory,這個(gè)松哥之前文章已經(jīng)和小伙伴們介紹過了(BeanFactoryPostProcessor 和 BeanPostProcessor 有什么區(qū)別?),BeanFactory 有一個(gè)子類 HierarchicalBeanFactory,看名字就是帶有層級(jí)關(guān)系的 BeanFactory:
public interface HierarchicalBeanFactory extends BeanFactory { /** * Return the parent bean factory, or {@code null} if there is none. */ @Nullable BeanFactory getParentBeanFactory(); /** * Return whether the local bean factory contains a bean of the given name, * ignoring beans defined in ancestor contexts. * This is an alternative to {@code containsBean}, ignoring a bean * of the given name from an ancestor bean factory. * @param name the name of the bean to query * @return whether a bean with the given name is defined in the local factory * @see BeanFactory#containsBean */ boolean containsLocalBean(String name);}
只要是 HierarchicalBeanFactory 的子類就能配置父子關(guān)系。父子關(guān)系圖如下:
圖片
2.3 特殊情況需要注意的是,并不是所有的獲取 Bean 的方法都支持父子關(guān)系查找,有的方法只能在當(dāng)前容器中查找,并不會(huì)去父容器中查找:
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext("merchant_beans.xml");child.setParent(parent);child.refresh();String[] names1 = child.getBeanNamesForType(org.javaboy.merchant.RoleService.class);String[] names2 = child.getBeanNamesForType(org.javaboy.consumer.RoleService.class);System.out.println("names1 = " + Arrays.toString(names1));System.out.println("names2 = " + Arrays.toString(names2));
如上,根據(jù)類型去查找 Bean 名稱的時(shí)候,我們所用的是 getBeanNamesForType 方法,這個(gè)方法是由 ListableBeanFactory 接口提供的,而該接口和 HierarchicalBeanFactory 接口并無繼承關(guān)系,所以 getBeanNamesForType 方法并不支持去父容器中查找 Bean,它只在當(dāng)前容器中查找 Bean。
但是!如果你確實(shí)有需求,希望能夠根據(jù)類型查找 Bean 名稱,并且還能夠自動(dòng)去父容器中查找,那么可以使用 Spring 給我們提供的工具類,如下:
ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext();child.setParent(parent);child.refresh();String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(child, org.javaboy.consumer.RoleService.class);for (String name : names) { System.out.println("name = " + name);}
不過這個(gè)查找,對(duì)于父子容器中同名的 Bean 是查找不出來名字的。
2.4 Spring 和 SpringMVC上面的內(nèi)容理解了,Spring 和 SpringMVC 之間的關(guān)系就好理解了,Spring 是父容器,SpringMVC 則是子容器。
在 SpringMVC 中,初始化 DispatcherServlet 的時(shí)候,會(huì)創(chuàng)建出 SpringMVC 容器,并且為 SpringMVC 容器設(shè)置 parent,相關(guān)代碼如下:
FrameworkServlet#initWebApplicationContext:
protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context. If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } return wac;}
這里的 rootContext 就是父容器,wac 就是子容器,無論哪種方式得到的子容器,都會(huì)嘗試給其設(shè)置一個(gè)父容器。
如果我們?cè)谝粋€(gè) Web 項(xiàng)目中,不單獨(dú)配置 Spring 容器,直接配置 SpringMVC 容器,然后將所有的 Bean 全部都掃描到 SpringMVC 容器中,這樣做是沒有問題的,項(xiàng)目是可以正常運(yùn)行的。但是一般項(xiàng)目中我們還是會(huì)把這兩個(gè)容器分開,分開有如下幾個(gè)好處:
方便管理,SpringMVC 主要處理控制層相關(guān)的 Bean,如 Controller、視圖解析器、參數(shù)處理器等等,而 Spring 層則主要控制業(yè)務(wù)層相關(guān)的 Bean,如 Service、Mapper、數(shù)據(jù)源、事務(wù)、權(quán)限等等相關(guān)的 Bean。對(duì)于新手而言,兩個(gè)容器分開配置,可以更好的理解 Controller、Service 以及 Dao 層的關(guān)系,也可以避免寫出來在 Service 層注入 Controller 這種荒唐代碼。另外再額外說一句,有的小伙伴可能會(huì)問,如果全部 Bean 都掃描到 Spring 容器中不用 SpringMVC 容器行不行?這其實(shí)也可以!但是需要一些額外的配置,這個(gè)松哥下篇文章再來和小伙伴們細(xì)述。
3. 小結(jié)好啦,Spring 容器中的父子容器現(xiàn)在大家應(yīng)該明白了吧?可以給非 ListableBeanFactory 容器設(shè)置父容器,父容器不可以訪問子容器的 Bean,但是子容器可以訪問父容器的 Bean。
關(guān)鍵詞:
相信有小伙伴也聽說過,在SSM項(xiàng)目中,Spring容器是父容器,SpringMVC是
1引言1 1簡(jiǎn)述輕質(zhì)檢報(bào)告的背景在去年,正式上線了對(duì)N品類的質(zhì)檢能力。
事件驅(qū)動(dòng)架構(gòu)是由生產(chǎn)者和消費(fèi)者組成,生產(chǎn)者負(fù)責(zé)生產(chǎn)事件,消費(fèi)者監(jiān)聽
cad測(cè)量面積快捷鍵為AA,計(jì)算面積的具體操作步驟如下:1 先打開CAD軟件
智通財(cái)經(jīng)APP獲悉,據(jù)報(bào)道,迪士尼(DIS US)正在探索旗下印度公司Star In
1、selina新男友是誰2、Selina懷孕8個(gè)月狀態(tài)3、以上就是關(guān)于【selina新
西湖區(qū)開展高溫天氣安全生產(chǎn)檢查杭州網(wǎng)發(fā)布時(shí)間:2023-07-1206:55杭州
小米藍(lán)牙耳機(jī)怎么連接手機(jī)小米藍(lán)牙耳機(jī)只有一邊有聲音怎么辦小米藍(lán)牙耳
想必現(xiàn)在有很多小伙伴對(duì)于駕照的副本丟了怎么辦方面的知識(shí)都比較想要了
電磁力,這個(gè)我們?nèi)粘I钪袩o處不在的力量,其提升卻常常被誤解和夸大
賈德松談贏球:我們通過自身的努力回報(bào)了球迷,因?yàn)樗麄冎档?上海申花,
新湖南客戶端7月11日訊(通訊員陳茜)7月7日,永州市零陵區(qū)接履橋鎮(zhèn)長(zhǎng)
“我被批評(píng)以胖子為中心而不顧及他人感受,被形容為不健康飲食的宣傳者
【英特爾第二代Gaudi深度學(xué)習(xí)加速器在中國(guó)市場(chǎng)上市】英特爾官微消息,7
可能描寫的微血腥,大家斟酌觀看“愛你 愛 愛 你 ”在一個(gè)看