亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Spring中的父子容器原理解析

 更新時間:2023年07月18日 09:31:49   作者:江南一點雨  
這篇文章主要為大家介紹了Spring中的父子容器原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

相信有小伙伴也聽說過,在 SSM 項目中,Spring 容器是父容器,SpringMVC 是子容器,子容器可以訪問父容器的 Bean,但是父容器不能訪問子容器的 Bean。

更近一步,有小伙伴可能也了解過,不用父子容器,單純就用一個 SpringMVC 容器似乎也可以,項目也能運行。

那么現在問題來了:既然單純一個 SpringMVC 容器就能使項目跑起來,那我們?yōu)槭裁催€要用父子容器?父子容器的優(yōu)勢是什么?

帶著這個問題,今天松哥來和小伙伴們聊一聊父子容器。

1. 父子容器

首先,其實父子這種設計很常見,松哥記得在之前的 Spring Security 的系列文章中,Spring Security 中的 AuthenticationManager 其實也是類似的設計,估計那里就是借鑒了 Spring 中的父子容器設計。

當使用了父子容器之后,如果去父容器中查找 Bean,那么就單純的在父容器中查找 Bean;

如果是去子容器中查找 Bean,那么就會先在子容器中查找,找到了就返回,沒找到則繼續(xù)去父容器中查找,直到找到為止(把父容器都找完了還是沒有的話,那就只能拋異常出來了)。

2. 為什么需要父子容器

2.1 問題呈現

為什么需要父子容器?老老實實使用一個容器不行嗎?

既然 Spring 容器中有父子容器,那么這個玩意就必然有其使用場景。

松哥舉一個簡單的例子。

假設我有一個多模塊項目,其中有商家模塊和客戶模塊,商家模塊和客戶模塊中都有角色管理 RoleService,項目結構如下圖:

├── 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

現在 consumer 和 merchant 中都有一個 RoleService 類,然后在各自的配置文件中,都將該類注冊到 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 如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="org.javaboy.consumer.RoleService" id="roleService"/>
</beans>

merchant_beans.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="org.javaboy.merchant.RoleService" id="roleService"/>
</beans>

大家注意,這兩個 Bean 同名。

現在,在 admin 模塊中,同時依賴 consumer 和 merchant,同時加載這兩個配置文件,那么能不能同時向 Spring 容器中注冊兩個來自不同模塊的同名 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);

這個執(zhí)行之后會拋出如下問題:

小伙伴們看到,這個是找不到 org.javaboy.consumer.RoleService 服務,但是另外一個 RoleService 其實是找到了,因為默認情況下后面定義的同名 Bean 把前面的覆蓋了,所以有一個 Bean 就找不到了。

如果不允許 Bean 的覆蓋,那么可以進行如下配置:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.setConfigLocations("consumer_beans.xml", "merchant_beans.xml");
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();

此時一啟動就直接報錯了:

意思也說的比較明確了,Bean 的定義沖突了,所以定義失敗。

那么有沒有辦法能夠優(yōu)雅的解決上面這個問題呢?答案就是父子容器!

2.2 父子容器

對于上面的問題,我們可以將 consumer 和 merchant 配置成父子關系或者兄弟關系,就能很好的解決這個問題了。

2.2.1 兄弟關系

先來看兄弟關系,代碼如下:

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());

小伙伴們看一下,這種針對 consumer 和 merchant 分別創(chuàng)建了容器,這種容器關系就是兄弟容器,這兩個兄弟有一個共同的 parent 就是 ctx,現在可以在各個容器中獲取到自己的 Bean 了。

需要注意的是,上面這種結構中,子容器可以獲取到 parent 的 Bean,但是無法獲取到兄弟容器的 Bean,即如果 consumer 中引用了 merchant 中的 Bean,那么上面這個配置就有問題了。

2.2.2 父子關系

現在假設用 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)建兩個容器,分別是 parent 和 child,然后為 child 容器設置 parent,設置完成后記得要刷新 child 容器。

現在我們就可以從 parent 容器中去獲取 parent 容器中原本就存在的 Bean,也可以從 child 容器中去獲取 child 容器原本的 Bean 或者是 parent 的 Bean 都可以。

這就是父子容器。

父容器和子容器本質上是相互隔離的兩個不同的容器,所以允許同名的 Bean 存在。當子容器調用 getBean 方法去獲取一個 Bean 的時候,如果當前容器沒找到,就會去父容器查找,一直往上找,找到為止。

核心就是 BeanFactory,這個松哥之前文章已經和小伙伴們介紹過了(BeanFactoryPostProcessor 和 BeanPostProcessor 有什么區(qū)別?),BeanFactory 有一個子類 HierarchicalBeanFactory,看名字就是帶有層級關系的 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.
     * &lt;p&gt;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 的子類就能配置父子關系。父子關系圖如下:

2.3 特殊情況

需要注意的是,并不是所有的獲取 Bean 的方法都支持父子關系查找,有的方法只能在當前容器中查找,并不會去父容器中查找:

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));

如上,根據類型去查找 Bean 名稱的時候,我們所用的是 getBeanNamesForType 方法,這個方法是由 ListableBeanFactory 接口提供的,而該接口和 HierarchicalBeanFactory 接口并無繼承關系,所以 getBeanNamesForType 方法并不支持去父容器中查找 Bean,它只在當前容器中查找 Bean。

但是!如果你確實有需求,希望能夠根據類型查找 Bean 名稱,并且還能夠自動去父容器中查找,那么可以使用 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);
}

不過這個查找,對于父子容器中同名的 Bean 是查找不出來名字的。

2.4 Spring 和 SpringMVC

上面的內容理解了,Spring 和 SpringMVC 之間的關系就好理解了,Spring 是父容器,SpringMVC 則是子容器。

在 SpringMVC 中,初始化 DispatcherServlet 的時候,會創(chuàng)建出 SpringMVC 容器,并且為 SpringMVC 容器設置 parent,相關代碼如下:

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 -&gt; use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext cwac &amp;&amp; !cwac.isActive()) {
            // The context has not yet been refreshed -&gt; 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 -&gt; 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 -&gt; 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 -&gt; create a local one
        wac = createWebApplicationContext(rootContext);
    }
    return wac;
}

這里的 rootContext 就是父容器,wac 就是子容器,無論哪種方式得到的子容器,都會嘗試給其設置一個父容器。

如果我們在一個 Web 項目中,不單獨配置 Spring 容器,直接配置 SpringMVC 容器,然后將所有的 Bean 全部都掃描到 SpringMVC 容器中,這樣做是沒有問題的,項目是可以正常運行的。但是一般項目中我們還是會把這兩個容器分開,分開有如下幾個好處:

  • 方便管理,SpringMVC 主要處理控制層相關的 Bean,如 Controller、視圖解析器、參數處理器等等,而 Spring 層則主要控制業(yè)務層相關的 Bean,如 Service、Mapper、數據源、事務、權限等等相關的 Bean。
  • 對于新手而言,兩個容器分開配置,可以更好的理解 Controller、Service 以及 Dao 層的關系,也可以避免寫出來在 Service 層注入 Controller 這種荒唐代碼。

另外再額外說一句,有的小伙伴可能會問,如果全部 Bean 都掃描到 Spring 容器中不用 SpringMVC 容器行不行?這其實也可以!但是需要一些額外的配置,這個松哥下篇文章再來和小伙伴們細述。

3. 小結

好啦,Spring 容器中的父子容器現在大家應該明白了吧?可以給非 ListableBeanFactory 容器設置父容器,父容器不可以訪問子容器的 Bean,但是子容器可以訪問父容器的 Bean。

以上就是Spring中的父子容器原理解析的詳細內容,更多關于Spring父子容器的資料請關注腳本之家其它相關文章!

相關文章

  • Java Web實現登錄頁面驗證碼驗證功能

    Java Web實現登錄頁面驗證碼驗證功能

    這篇文章主要介紹了Java Web登錄頁面驗證碼驗證功能,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-12-12
  • Java實現與JS相同的Des加解密算法完整實例

    Java實現與JS相同的Des加解密算法完整實例

    這篇文章主要介紹了Java實現與JS相同的Des加解密算法,結合完整實例形式分析了java及js實現des加密與應用的具體操作技巧,需要的朋友可以參考下
    2017-11-11
  • Spring?Security實現添加圖片驗證功能

    Spring?Security實現添加圖片驗證功能

    這篇文章主要為大家介紹了Spring?Security實現添加圖片驗證功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • Idea2023配置tomcat服務器的圖文教程

    Idea2023配置tomcat服務器的圖文教程

    這篇文章主要介紹了Idea2023配置tomcat服務器的圖文教程,本文是javaweb新手版教程,IDEA2023+JDK1.8+apache-tomcat-8.5.91,沒有使用Maven,需要的朋友可以參考下
    2023-10-10
  • Java的內存分配與回收策略詳解

    Java的內存分配與回收策略詳解

    這篇文章主要介紹了Java的內存分配與回收策略詳解,對象的內存分配,就是在堆上分配,對象主要分配在新生代的 Eden 區(qū)上,少數情況下可能直接分配在老年代,分配規(guī)則不固定,取決于當前使用的垃圾收集器組合以及相關的參數配置,需要的朋友可以參考下
    2023-08-08
  • springBoot熱部署、請求轉發(fā)與重定向步驟詳解

    springBoot熱部署、請求轉發(fā)與重定向步驟詳解

    這篇文章主要介紹了springBoot熱部署、請求轉發(fā)與重定向,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • Java匿名內部類的使用方法舉例詳解

    Java匿名內部類的使用方法舉例詳解

    Java中的匿名內部類是一種沒有名字的局部內部類,主要用于一次性實現接口或繼承類的場合,它們常見于GUI事件處理、多線程編程等場景,簡化代碼結構同時提高開發(fā)效率,需要的朋友可以參考下
    2024-09-09
  • Java并發(fā)系列之ReentrantLock源碼分析

    Java并發(fā)系列之ReentrantLock源碼分析

    這篇文章主要為大家詳細介紹了Java并發(fā)系列之ReentrantLock源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • Spring的IOC原理詳情

    Spring的IOC原理詳情

    這篇文章主要介紹了Spring的IOC原理詳情,IOC是Inversion?of?Control的縮寫,多數書籍翻譯成“控制反轉”,還有些書籍翻譯成為控制反向或者控制倒置
    2022-07-07
  • Mybatis之Mapper動態(tài)代理實例解析

    Mybatis之Mapper動態(tài)代理實例解析

    這篇文章主要介紹了Mybatis之Mapper動態(tài)代理實例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-08-08

最新評論