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 的使用及其设计实现。有问题欢迎留言探讨。


目录
相关文章
|
1月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
57 2
|
2月前
|
缓存 前端开发 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版)
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
133 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
71 0
|
2月前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
135 3
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
3月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
3月前
|
前端开发 应用服务中间件 数据库
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
这篇文章通过一个具体的项目案例,详细讲解了如何使用SpringMVC、Thymeleaf、Bootstrap以及RESTful风格接口来实现员工信息的增删改查功能。文章提供了项目结构、配置文件、控制器、数据访问对象、实体类和前端页面的完整源码,并展示了实现效果的截图。项目的目的是锻炼使用RESTful风格的接口开发,虽然数据是假数据并未连接数据库,但提供了一个很好的实践机会。文章最后强调了这一章节主要是为了练习RESTful,其他方面暂不考虑。
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
下一篇
无影云桌面