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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家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


相关文章
|
3月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
296 70
|
4月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
6月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
229 7
|
前端开发 Java BI
Spring3 Web MVC 集成Jasper Report生成PDF例子
Spring3 Web MVC 集成Jasper Report生成PDF例子
117 7
|
JSON 前端开发 Java
Spring Web MVC入门(3)——响应
Spring Web MVC入门(3)——响应
97 1
|
存储 前端开发 Java
Spring Web MVC入门(2)——请求(下)
Spring Web MVC入门(2)——请求
107 0
|
JSON 前端开发 Java
Spring Web MVC入门(2)——请求(上)
Spring Web MVC入门(2)——请求
78 0
|
设计模式 前端开发 Java
Spring Web MVC入门(1)
Spring Web MVC入门(1)
74 0
|
前端开发 Java 应用服务中间件
【JavaEE进阶】 初识Spring Web MVC
【JavaEE进阶】 初识Spring Web MVC
|
前端开发 Java 应用服务中间件
SpringMVC(基于Spring 的Web 层MVC 框架)--SpingMVC 执行流程--@RequestMapping的使用
SpringMVC(基于Spring 的Web 层MVC 框架)--SpingMVC 执行流程--@RequestMapping的使用
190 0