【小家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


相关文章
|
24天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
64 2
|
1月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
10天前
|
XML Java 数据格式
Spring Core核心类库的功能与应用实践分析
【12月更文挑战第1天】大家好,今天我们来聊聊Spring Core这个强大的核心类库。Spring Core作为Spring框架的基础,提供了控制反转(IOC)和依赖注入(DI)等核心功能,以及企业级功能,如JNDI和定时任务等。通过本文,我们将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring Core,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
33 14
|
29天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
53 9
|
26天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
84 2
|
2天前
|
监控 NoSQL 时序数据库
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
118 77
|
4天前
|
数据建模 应用服务中间件 nginx
docker替换宿主与容器的映射端口和文件路径
通过正确配置 Docker 的端口和文件路径映射,可以有效地管理容器化应用程序,确保其高效运行和数据持久性。在生产环境中,动态替换映射配置有助于灵活应对各种需求变化。以上方法和步骤提供了一种可靠且易于操作的方案,帮助您轻松管理 Docker 容器的端口和路径映射。
29 3
|
16天前
|
Kubernetes Linux 开发者
深入探索容器化技术——Docker 的实战应用
深入探索容器化技术——Docker 的实战应用
46 5
|
20天前
|
运维 Cloud Native 云计算
云原生之旅:Docker容器化实战
本文将带你走进云原生的世界,深入理解Docker技术如何改变应用部署与运维。我们将通过实际案例,展示如何利用Docker简化开发流程,提升应用的可移植性和伸缩性。文章不仅介绍基础概念,还提供操作指南和最佳实践,帮助你快速上手Docker,开启云原生的第一步。
|
23天前
|
机器学习/深度学习 数据采集 Docker
Docker容器化实战:构建并部署一个简单的Web应用
Docker容器化实战:构建并部署一个简单的Web应用