【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)(下)

简介: 【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)(下)

基于web.xml方式


最常用配置如下:


<listener>  
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
</listener>
<context-param>  
    <param-name>contextConfigLocation</param-name>  
    <param-value>classpath:spring/applicationContext.xml</param-value>  
</context-param>
<!-- 配置DispatcherServlet -->  
<servlet>  
  <servlet-name>springMvc</servlet-name>  
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  <!-- 指定spring mvc配置文件位置 不指定使用默认情况 -->  
  <init-param>     
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/spring-mvc.xml</param-value>
   </init-param>  
  <!-- 设置启动顺序 1表示立即启动,而不是首次访问再启动-->  
  <load-on-startup>1</load-on-startup>  
  <async-supported>true</async-supported>
</servlet>
<servlet-mapping>
  <servlet-name>springMvc</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

image.png


注意:如果没有指定spring-mvc.xml 配置,则默认使用DispatcherServlet的默认配置DispatcherServlet.properties


# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
  org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
  org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
  org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
  org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
  org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager


有了上面基于注解的分析,这里就非常容易了。注解驱动,容器都是它自己根据配置类进行创建的,而此处基于xml的形式,我们只需要区别的看两个创建方法即可,上面也已经提到了:

ContextLoader#createWebApplicationContext() 根据xml创建根容器

  protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 找到上下文类型。若自己没有配置实现类,那就是XmlApplicationContext
    Class<?> contextClass = determineContextClass(sc);
    // 由此看出  用户diy的也必须是ConfigurableWebApplicationContext的子类才行
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
          "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    // 使用无参构造实例化一个实例
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  }

determineContextClass方法如下:


  protected Class<?> determineContextClass(ServletContext servletContext) {
    // 显然,一般情况下我们都不会自己配置一个容器类,自己去实现~ 所有走else
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
      try {
        return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }
    } else {
      // 采用默认配置文件里的XmlApplicationContext来初始化上下文
      contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
      try {
        return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
      }
    }
  }


FrameworkServlet#initWebApplicationContext


涉及到这两个方法:


  protected WebApplicationContext findWebApplicationContext() {
    // 只有调用过setContextAttribute(String contextAttribute)这里才有值,否则为null  或者web.xml里配置:contextAttribute为key的属性值
    String attrName = getContextAttribute();
    if (attrName == null) {
      return null;
    }
    //按照这个attr去Servlet容器里面找
    WebApplicationContext wac =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
    if (wac == null) {
      throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
    }
    return wac;
  }


这里一般都会返回null,因此此处继续创建:


  protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    // 没有配置的话,默认值为public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
    Class<?> contextClass = getContextClass();
    // 校验必须是ConfigurableWebApplicationContext的子类
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException(
          "Fatal initialization error in servlet with name '" + getServletName() +
          "': custom WebApplicationContext class [" + contextClass.getName() +
          "] is not of type ConfigurableWebApplicationContext");
    }
    // 创建一个容器实例
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    // 设置好父容器、Enviroment等等
    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    //看看有没有配置配置文件的位置
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
      wac.setConfigLocation(configLocation);
    }
    // 这个是重点,如完善、初始化、刷新容器
    configureAndRefreshWebApplicationContext(wac);
    return wac;
  }


configureAndRefreshWebApplicationContext()如下


  protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      if (this.contextId != null) {
        wac.setId(this.contextId);
      } else {
        // 默认的id  这里面和contextpath有关了
        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
            ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
    }
    // 关联到了Namespace/servlet等等
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    //添加了一个容器监听器  此监听器SourceFilteringListener在后面还会碰到
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    // 同之前~
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
    //留给子类,可以复写此方法,做一些初始化时候自己的实行
    postProcessWebApplicationContext(wac);
    //同样的 执行一些初始化类们  一般也是用不着。备注:用到了这个常量ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM
    applyInitializers(wac);
    wac.refresh();
  }


至此,web子容器就全部创建、刷新完成了~

Spring父子容器的优缺点


优点:能让web环境和普通的Spring环境达到隔离的效果。web容器专注于管理web相关Bean,其余的交给父容器即可。 这样子强制隔离,也能驱动我们在编码过程中注重分层,使得层次结构更加的明晰

缺点:父子容器的设计提高了Spring初始化、管理Bean的复杂度(虽然对我们使用者一般都无感),我们万一要用到相关功能的时候,若不理解原理会有莫名其妙的一些问题,提高了复杂性


理论上我们可以有任意多个容器(只是我们一般其它的都只放进主容器统一管理上,但Spring是提供了这样的功能的),比如


   主容器:applicationContext.xml(主文件,包括JDBC配置,hibernate.cfg.xml,与所有的Service与DAO基类)


   web子容器:application-servlet.xml(管理Spring MVC9打组件以及相关的Bean)


   cache子容器:applicationContext-cache.xml(cache策略配置,管理和缓存相关的Bean)


   JMX子容器:applicationContext-jmx.xml(JMX相关的Bean)

总之:通过父子容器分层管理是好的设计思想,但是在编码使用中,大道至简才是我们追求的方式。所以我个人觉得:父子容器的设计并不是一根很好的设计(理想是好的,但实施上却不见得是好的),估计这也是为何Spring Boot中只采用一个容器的原因吧,简单的或许就是最好的(仅个人意见,不喜勿喷)


总结


本篇文章基本介绍了Spring容器以及Spring MVC容器的一个初始化过程,包括了web.xml和注解驱动两种方式。


然后里面涉及到的Spring容器刷新过程的核心逻辑,以及Dispatcher的9打组件,以及处理请求的过程,请关注接下来的博文~


值得注意的是,springMVC在调用HandlerMapper进行url到controller函数方法映射解析的时候,HandlerMapper会在springMVC容器中寻找controller,也就是在子容器中寻找,不会去父容器spring容器中寻找的。

所以如果用父容器来管理controller的话,子容器不去管理,在访问页面的时候会出现404错误。


所以我们姑且可以这么认为:我们可以用子容器统一管理Bean(包括父容器的Bean),但是不能用父容器管理所有Bean


相关文章
|
7月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
835 6
|
9月前
|
运维 数据可视化 C++
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
2025年热门Web化容器部署工具对比:Portainer与Websoft9。Portainer以轻量可视化管理见长,适合技术团队运维;Websoft9则提供一站式应用部署与容器管理,内置丰富开源模板,降低中小企业部署门槛。两者各有优势,助力企业提升容器化效率。
591 1
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
|
XML 存储 JSON
Twaver-HTML5基础学习(19)数据容器(2)_数据序列化_XML、Json
本文介绍了Twaver HTML5中的数据序列化,包括XML和JSON格式的序列化与反序列化方法。文章通过示例代码展示了如何将DataBox中的数据序列化为XML和JSON字符串,以及如何从这些字符串中反序列化数据,重建DataBox中的对象。此外,还提到了用户自定义属性的序列化注册方法。
270 1
|
11月前
|
负载均衡 Java API
基于 Spring Cloud 的微服务架构分析
Spring Cloud 是一个基于 Spring Boot 的微服务框架,提供全套分布式系统解决方案。它整合了 Netflix、Zookeeper 等成熟技术,通过简化配置和开发流程,支持服务发现(Eureka)、负载均衡(Ribbon)、断路器(Hystrix)、API网关(Zuul)、配置管理(Config)等功能。此外,Spring Cloud 还兼容 Nacos、Consul、Etcd 等注册中心,满足不同场景需求。其核心组件如 Feign 和 Stream,进一步增强了服务调用与消息处理能力,为开发者提供了一站式微服务开发工具包。
940 0
|
SQL 前端开发 Java
深入分析 Spring Boot 项目开发中的常见问题与解决方案
本文深入分析了Spring Boot项目开发中的常见问题与解决方案,涵盖视图路径冲突(Circular View Path)、ECharts图表数据异常及SQL唯一约束冲突等典型场景。通过实际案例剖析问题成因,并提供具体解决方法,如优化视图解析器配置、改进数据查询逻辑以及合理使用外键约束。同时复习了Spring MVC视图解析原理与数据库完整性知识,强调细节处理和数据验证的重要性,为开发者提供实用参考。
518 0
|
XML Java 数据格式
Spring Core核心类库的功能与应用实践分析
【12月更文挑战第1天】大家好,今天我们来聊聊Spring Core这个强大的核心类库。Spring Core作为Spring框架的基础,提供了控制反转(IOC)和依赖注入(DI)等核心功能,以及企业级功能,如JNDI和定时任务等。通过本文,我们将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring Core,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
240 14
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
534 6
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
4594 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
开发者 Docker Python
从零开始:使用Docker容器化你的Python Web应用
从零开始:使用Docker容器化你的Python Web应用
775 4
|
机器学习/深度学习 数据采集 Docker
Docker容器化实战:构建并部署一个简单的Web应用
Docker容器化实战:构建并部署一个简单的Web应用