Spring MVC 父子容器是什么?这篇文章讲清楚了

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: Spring MVC 父子容器是初学 Spring MVC 时最先接触到 Spring 知识点之一,还记得我刚工作那会,项目基础架构是其他同事搭建的,其中就用到了 Spring MVC 中的父子容器,还把 Spring MVC 中的不同层拆成了不同的 maven 模块。这里暂不讨论这种模块拆分方式的优劣,Spring 为什么设计出具有层次结构的容器呢?Web 环境中什么场景会用到这种具有层次结构的容器?

Spring MVC 父子容器是初学 Spring MVC 时最先接触到 Spring 知识点之一,还记得我刚工作那会,项目基础架构是其他同事搭建的,其中就用到了 Spring MVC 中的父子容器,还把 Spring MVC 中的不同层拆成了不同的 maven 模块。这里暂不讨论这种模块拆分方式的优劣,Spring 为什么设计出具有层次结构的容器呢?Web 环境中什么场景会用到这种具有层次结构的容器?


Spring 父子容器是什么?


父子容器并非 Spring MVC 的专利,在普通的 Spring 环境下 Spring 就已经设计出具有层次结构的容器了,这种设计方式也并非 Spring 独创,其工作方式和 ClassLoader 很相似,每个容器有一个自己的父容器,但是与 ClassLoader 不同的是,通过容器查找 bean 时是优先从子容器查找,如果找不到才会从父容器中查找。当应用中存在多个容器时,这种设计方式可以将公共的 bean 放到父容器中,如果父容器中的 bean 不适用,子容器还可以覆盖父容器中的 bean。


Spring 中的容器有两种,一种是常用的 ApplicationContext,父子容器相关的层次结构如下。


image.png

另一种容器是最底层的 BeanFactory,ApplicationContext 就依托于底层的 BeanFactory 查找 Bean。

image.png

BeanFactory 最终的实现是 DefaultListableBeanFactory,其查找 bean 的部分源码如下,从中可以看出 Spring 是优先从子容器中查找 bean 的,如果查不到会再次从父容器中查找。


11.png


Spring MVC 环境下父子容器应用场景


Spring MVC 负责控制整个请求流程的核心类是 DispatcherServlet,这个 DispatcherServlet 会关联一个 ApplicationContext,通常情况下,一个应用中有一个 DispatcherServlet 就足够了。


但是呢,凡事都有例外,如果你正在做一个商城,不同的模块如商品、购物车、订单模块使用了不同的 DispatcherServlet 来处理请求,这就意味着一个应用中出现了多个 ApplicationContext,由于每个 ApplicationContext 是独立的,因此订单模块就不能直接使用商品或购物车模块的服务来下单,订单模块把商品或购物车模块的服务注册到自身所在的容器中虽然能解决问题,但是这也意味着多个容器中保存了多个相同类型的 bean,那怎么解决呢?将这些公共的服务注册到相同的父容器中,这样每个子容器都能使用到父容器中的公共 bean。


image.png


ApplicationContext 在 Spring MVC 环境下的实现是其子接口 WebApplicationConetxt,这个公共的 WebApplicationContext 也被称为 Root WebApplicationContext。通过父子容器对 bean 进行拆分之后,可以将与 Web 环境有关的 bean ,如 Controller、ViewResolver、HandlerMapping 等保存到 DispatcherServlet 中的 WebApplicationContext,而跨越多个 DispatcherServlet 共享的 Service、Repository bean 可以存放到 Root WebApplicationContext。


使用父容器之后的商城应用,自然而然,负责订单的 DispatcherServlet 子容器中的 Controller 可以使用父容器中的购物车 Service,达到了复用 bean 的目的。


Spring MVC 环境下父子容器的初始化过程


Spring 子容器依赖父容器,因此需要先对父容器初始化,然后才能对子容器初始化。Spring MVC是怎么保证父子容器初始化顺序的呢?


Spring MVC 依托于 Servlet 规范。Servlet 规范中 ,所有的 Servlet 具有一个相同的上下文 ServletContext,ServletContext 将优先于 Servlet 初始化,Spring 利用了这个特性,在 ServletContext 初始化时创建父容器,并将其绑定到 ServletContext 的属性中,然后在每个 DispatcherServlet 初始过程中创建子容器并将 ServletContext 中的容器设置为父容器。


父容器初始化


Servlet 容器会在 ServletContext 初始化时触发事件,然后由 ServletContextListener 监听,Spring 提供了这个接口的实现 ContextLoaderListener 并在初始化时创建父容器。代码如下。


public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
  ... 省略部分代码
  @Override
  public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext());
  }
}


ContextLoaderListener 仅调用了父类 ContextLoader 中的 initWebApplicationContext方法创建容器,该方法会将创建后的容器存至名为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的属性中。


子容器初始化


子容器由 DispatcherServlet 进行初始化,简化后的类图如下所示。


image.png


首先 Spring 提供的 HttpServletBean 继承 HttpServlet 类,重写了 init 方法,并调用了抽象方法 initServletBean。

然后 HttpServletBean 的子类 FrameworkServlet 重写了 initServletBean 方法,并调用了 initWebApplicationContext 方法,这个方法将会完成子容器的初始化工作。子容器初始化时从 ServletContext 属性中取出并设置父容器。

DispatcherServlet 直接使用了父类 FrameworkServlet 的初始化方法。

Spring MVC 环境下父子容器配置

使用 Spring 需要将 bean 注入 Spring 容器,针对 web 环境,配置父容器需要使用 ContextLoaderListener,配置子容器需要使用 DispatcherServlet。具体来分又有 xml 和注解两种配置方式。


xml 配置 Spring MVC 容器


传统的 Java Web 应用需要将应用中使用的组件清单配置到 web.xml 文件中,针对 Spring 容器的配置如下。


<web-app>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/app1-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>
</web-app>


ContextLoaderListener 被配置到监听器列表,ServletContext 初始化时会使用 context-param 中参数名为 contextConfigLocation 的值作为配置文件路径初始化容器。


DispatcherServlet 也需要添加到 Servlet 列表,DispatcherServlet 同时也会使用初始化参数 contextConfigLocation 作为配置文件的路径初始化容器。


注解配置 Spring MVC 容器


为了减少手工在 web.xml 文件进行配置的工作,Servlet 3.0 提供了一个 ServletContainerInitializer 接口,Servlet 容器启动时会扫描类路径,并在容器初始化时回调这个接口中的方法,将用户感兴趣的类型传递到方法参数中。这个接口的定义如下。


public interface ServletContainerInitializer {
    public void onStartup(Set<Class<?>> c, ServletContext ctx)  throws ServletException; 
}


spring-framework 3.1 开始支持了这个特性,提供了一个 SpringServletContainerInitializer 类,这个类会对 WebApplicationInitializer 类型进行处理,因此将 WebApplicationInitializer 的实现直接添加到类路径中即可。WebApplicationInitializer 接口定义如下。


public interface WebApplicationInitializer {
  void onStartup(ServletContext servletContext) throws ServletException;
}


为了便于配置上下文及 DispatcherServlet,Spring 提供了 WebApplicationInitializer 的实现类 AbstractAnnotationConfigDispatcherServletInitializer,因此我们实现这个类就可以配置上下文。


和上述 web.xml 文件等价的配置如下。


public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { App1Config.class };
    }
    @Override
    protected String[] getServletMappings() {
        return new String[] { "/app1/*" };
    }
}


etRootConfigClasses 方法返回的是根应用上下文的配置,getServletConfigClasses 方法返回的是 DispatcherServlet 的上下文配置,getServletMappings 则用来指定 DispatcherServlet 处理的路径。


总结

Spring MVC 上下文虽然使用场景上来说并不多,但它却是了解 Spring MVC 必不可少的内容。后面会继续深入探索 Spring MVC 的使用及其设计实现。有问题欢迎留言探讨。


目录
相关文章
|
1月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
6天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
30天前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
1月前
|
XML JSON 数据库
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
这篇文章详细介绍了RESTful的概念、实现方式,以及如何在SpringMVC中使用HiddenHttpMethodFilter来处理PUT和DELETE请求,并通过具体代码案例分析了RESTful的使用。
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
|
1月前
|
XML Java 数据格式
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
这篇文章是Spring5框架的入门教程,详细讲解了IOC容器中Bean的自动装配机制,包括手动装配、`byName`和`byType`两种自动装配方式,并通过XML配置文件和Java代码示例展示了如何在Spring中实现自动装配。
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
|
1月前
|
XML Java 数据格式
Spring5入门到实战------2、IOC容器底层原理
这篇文章深入探讨了Spring5框架中的IOC容器,包括IOC的概念、底层原理、以及BeanFactory接口和ApplicationContext接口的介绍。文章通过图解和实例代码,解释了IOC如何通过工厂模式和反射机制实现对象的创建和管理,以及如何降低代码耦合度,提高开发效率。
Spring5入门到实战------2、IOC容器底层原理
|
1月前
|
前端开发 应用服务中间件 数据库
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
这篇文章通过一个具体的项目案例,详细讲解了如何使用SpringMVC、Thymeleaf、Bootstrap以及RESTful风格接口来实现员工信息的增删改查功能。文章提供了项目结构、配置文件、控制器、数据访问对象、实体类和前端页面的完整源码,并展示了实现效果的截图。项目的目的是锻炼使用RESTful风格的接口开发,虽然数据是假数据并未连接数据库,但提供了一个很好的实践机会。文章最后强调了这一章节主要是为了练习RESTful,其他方面暂不考虑。
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
|
1月前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式
|
1月前
|
Java Spring 容器
建模底层逻辑问题之以Spring IOC容器为例,使用因果法建模,如何操作
建模底层逻辑问题之以Spring IOC容器为例,使用因果法建模,如何操作
|
6天前
|
弹性计算 运维 持续交付
探索Docker容器化技术及其在生产环境中的应用
探索Docker容器化技术及其在生产环境中的应用
36 5