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

本文涉及的产品
容器镜像服务 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 的使用及其设计实现。有问题欢迎留言探讨。


目录
相关文章
|
22天前
|
Ubuntu Linux 开发工具
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包成标准化单元(容器),确保在任何支持 Docker 的操作系统上一致运行。容器共享主机内核,提供轻量级、高效的执行环境。本文介绍如何在 Ubuntu 上安装 Docker,并通过简单步骤验证安装成功。后续文章将探讨使用 Docker 部署开源项目。优雅草央千澈 源、安装 Docker 包、验证安装 - 适用场景:开发、测试、生产环境 通过以上步骤,您可以在 Ubuntu 系统上成功安装并运行 Docker,为后续的应用部署打下基础。
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
|
1月前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
66 6
|
1月前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
50 4
|
1月前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
41 1
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
188 2
|
2月前
|
前端开发 Java Docker
使用Docker容器化部署Spring Boot应用程序
使用Docker容器化部署Spring Boot应用程序
|
2月前
|
Java Docker 微服务
利用Docker容器化部署Spring Boot应用
利用Docker容器化部署Spring Boot应用
63 0
|
3月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
3月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
3月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
91 0