本文主要讲解的是SpringMVC不使用web.xml文件,而是使用JavaConfig的方式完成对SpringMVC配置的配置。即换一种方式完成DispatcherServlet等类的配置。

参考文章:SpringMVC 通过java类配置(不通过web.xml和xml 配置文件方式)

通过本文,你将了解到:
1. SpringMVC和Spring之间如何使用父子容器进行连接
2. SpringMVC的ServletContextListener和DispatcherServlet是在什么时候进行加载,以及加载了什么东西

文章目录

  • 一、JDK的SPI机制
  • 二、SpringServletContainerInitializer
    • 1、@HandlesTypes(WebApplicationInitializer.class)
    • 2、WebApplicationInitializer
    • 3、AbstractDispatcherServletInitializer
  • 三、ServletContextListener初始化
    • 1、contextInitialized
    • 2、configureAndRefreshWebApplicationContext
  • 四、HttpServlet初始化
    • 1、init
    • 2、initWebApplicationContext
    • 3、configureAndRefreshWebApplicationContext
    • 4、ContextRefreshListener

一、JDK的SPI机制

首先你需要明白这个SPI机制,即了解我们的配置类是在什么地方被调用的

我看到网上有人写的挺详细的,如果你还不知道这个机制,可以参考参考这篇文章:深入理解SPI机制

总结下来就是一句话,JDK会自动加载META-INF/services目录下的类。

如JDBC中的:

又如本篇文章的重点SpringMVC:

我们的故事就从这里说起


二、SpringServletContainerInitializer

以onStartup方法为入口开始探索

1、@HandlesTypes(WebApplicationInitializer.class)

在类上有一个@HandlesTypes注解,该注解的作用为,获取到所有的实现了WebApplicationInitializer接口的类,然后赋值给onStartup方法的webAppInitializerClasses参数。官方话术为,获取当前类(SpringServletContainerInitializer)感兴趣的类(WebApplicationInitializer)信息。

2、WebApplicationInitializer

下图是WebApplicationInitializer接口的继承体系,

测试类可以直接忽略,并且只关注AbstractContextLoaderInitializer这条继承链(上面部分)就好了

前文介绍了@HandlesTypes注解的作用,最终的效果就是,在符合不是接口、不是抽象类等要求的基础上,执行实现了WebApplicationInitializer接口的类的onStartup方法。我们不难发现,系统默认的接口实现类都是不符合要求的,这就是我们通过JavaConfig方式配置的流程中有一步是继承抽象类AbstractAnnotationConfigDispatcherServletInitializer,然后重写对应的方法即可。

然后以我们自定义的类为起点,加载对应的onStartup方法,它会调用到它父类的父类中的onStartup方法(父类的父类中有实现)

该层级的设计是基于模板方法的设计模式进行设计的。为了方便查看,我根据类的关系和类中方法的作用整理成了下面的结构图:

由图我们可以得出:

  1. 我们需要自己去实现对应的父容器及子容器的配置信息(指定对应的配置类)
  2. AbstractContextLoaderInitializer类中完成ContextLoader的注册初始化
  3. AbstractDispatcherServletInitializer类中完成前端控制器DispatcherServlet的注册初始化
  4. 不算我们自定义的类,一共四层,每层完成指定的操作。上面的2、3两点就是其中的两层操作,第4层的操作为创建容器,但是创建容器的过程中需要获取配置信息,这个数据来源于我们自定义的类,你也可以理解为第五层

只要你明白了这个层级关系,剩下的代码就很简单了。一定要多理几遍这个关系,这对于后面的整体理解都起着至关重要的作用

3、AbstractDispatcherServletInitializer

主要的调用逻辑由该类的onStartup方法开始。

首先会调用父类的onStartup方法,完成父容器的初始化基础工作。主要工作为创建父容器、创建ContextLoaderListener

然后调用当前类的registerDispatcherServlet方法。主要工作为创建MVC子容器、创建DispatcherServlet

1)父类初始化步骤

初始化ContextLoaderListener的时候传入的参数为父容器。这在第三节的赋值阶段会再次使用到

代码片段在AbstractContextLoaderInitializer

2)当前类初始化步骤

创建DispatcherServlet方法的时候,传入的是子容器。

代码片段在AbstractDispatcherServletInitializer类中

3)创建子容器

即是去拿子类的配置文件(我们自己实现的config配置类),完成创建

代码片段在AbstractAnnotationConfigDispatcherServletInitializer类中

以上的过程,我们就可以等价于使用xml形式的配置Spring

这个过程中还有两个重点,分别是ContextLoaderListener和DispatcherServlet,我在后面单独讲解


三、ServletContextListener初始化

本节对应的是前面提到的ContextLoaderListener

前面只是完成对该类的实例化,即已经完成相关参数的赋值。在启动Tomcat的时候,根据Listener的生命周期,容器我们会调用ContextLoaderListener类中的contextInitialized方法,执行相关的逻辑。

1、contextInitialized

该方法主要功能为初始化父容器(根容器),并把父容器添加到servletContext的上下文中

代码片段在ContextLoader类中

2、configureAndRefreshWebApplicationContext

配置contextConfigLocation,启动父容器

代码片段在ContextLoader类中

至此,父容器相关的准备工作已经处理完成


四、HttpServlet初始化

本节对应的是前面的DispatcherServlet

我们都知道SpringMVC的底层就是封装了Servlet。以至于我们在编写Controller层接口的时候,不再是去继承HttpServlet 类,重写对应的doGet、doPost方法…

现在我们只需要添加一个@Controller注解,添加对应的@RequestMapping映射即可完成一个接口的编写。这一切的一切都要从Spring为我们编写的一个Servlet说起,他就是DispatcherServlet。而这个类就是我们前面在创建子容器的时候初始化出来的

查看该类的继承关系,我们可以得到如下的关系图谱。而对应的调用关系也从这里开始。

这里需要补充Servlet的生命周期说明

Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:

  • Servlet 初始化后调用 init () 方法。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 销毁前调用 destroy() 方法。
  • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

1、init

即我们Servlet方法的调用位置为init方法,此时跳转到HttpServletBean类的init方法

该代码片段在HttpServletBean类中

接下来的几步调用都比较常规

init() --> initServletBean() --> initWebApplicationContext()

2、initWebApplicationContext

该方法主要功能为:设置MVC子容器和根容器的父子关系。加载子容器

该代码片段在FrameworkServlet类中

3、configureAndRefreshWebApplicationContext

该类可以类比理解在Listener步骤的初始化容器步骤,毕竟方法的名字都是一样的,只是来自不同的类。Listener类中初始化父容器,Servlet类中初始化子容器(MVC的容器)

该步骤会注册监听器,注意只是完成注册,具体的监听器方法的调用位置不在这里。监听器调用的位置在fresh方法中

该代码片段在FrameworkServlet类中

4、ContextRefreshListener

监听器是一个十分重要的点,因为后面DispatcherServlet在处理器映射器、处理器适配器、视图解析器的数据转发的时候会用到这些初始化map中的数据

对应的方法跳转逻辑:onApplicationEvent --> onRefresh --> onRefresh --> initStrategies

该代码片段在FrameworkServlet类中

选择其中一个map数据进行讲解,下图是初始化HandlerMapping的数据:

  1. detectAllHandlerMappings默认为true,即进入第一个判断
  2. 获取容器中,实现了HandlerMapping接口的所有Bean,赋值给handlerMappings
  3. 即未来我们获取使用handlerMappings的时候,是有值的,赋值的位置就在这里

该代码片段在FrameworkServlet类中

我们可以看到HandlerMapping接口的实现中,就有我们熟悉的BeanNameUrlHandlerMapping和RequestMappingHandlerMapping

至此,SpringMVC所需要的前期准备工作大致已经完成。

浅谈SpringMVC源码的SpringServletContainerInitializer的完整加载流程相关推荐

  1. 源码解析 --skywalking agent 插件加载流程

    1. 插件 目前很多框架,都采用框架 + 插件的模式开发. 如DataX.FlinkX通过插件支持众多异构数据源, Skywalking通过插件实现针对很多软件如redis.mysql.dubbo等方 ...

  2. php wrappers,浅谈PHP源码六:关于stream_get_wrappers函数

    这篇文章主要介绍了关于浅谈PHP源码六:关于stream_get_wrappers函数,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下 stream_get_wrappers (PHP ...

  3. splice php,浅谈PHP源码二十二:关于array_splice函数

    这篇文章主要介绍了关于 浅谈PHP源码二十二:关于array_splice函数,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下 浅谈PHP源码二十二:关于array_splice函数 a ...

  4. 直播app系统源码通过CSS液体实现加载动画

    直播app系统源码通过CSS液体实现加载动画 首先我们要让元素能够旋转起来,可以使用transform中的rotate进行2D的360deg旋转. 紧接着我们可以通过CSS变量(–开头的形式)结合an ...

  5. python控制浏览器不上下滚动失灵_浅谈selenium如何应对网页内容需要鼠标滚动加载的问题...

    相信大家在selenium爬取网页的时候都遇到过这样的问题:就是网页内容需要用鼠标滚动加载剩余内容,而不是一次全部加载出网页的全部内容,这个时候如果要模拟翻页的时候就必须加载出全部的内容,不然定位元素 ...

  6. JVM源码阅读-Dalvik类的加载

    前言 本文主要研究Android dalvik虚拟机加载类的流程和机制.目的是了解Android中DEX文件结构,虚拟机如何从DEX文件中加载一个Java Class,以及到最终如何初始化这个类直至可 ...

  7. python flask源码解析_浅谈flask源码之请求过程

    Flask Flask是什么? Flask是一个使用 Python 编写的轻量级 Web 应用框架, 让我们可以使用Python语言快速搭建Web服务, Flask也被称为 "microfr ...

  8. 浅谈jquery源码解析

    本文主要是针对jquery  v3.x版本来描述的,将从以下几个方面谈谈我对jquery的认识, 总体架构 $与$.fn jQuery.fn.init  (重要) jQuery.extend  与jQ ...

  9. 浅谈android源码之device

    文章目录 1. 前言 2. 目录结构 3. 其他 1. 前言 device目录为平台产品定制目录,这边将以某平台为例,讲一下结构设计思路,但这里并不会涉及到源码设计,仅供参考. 2. 目录结构 定制的 ...

  10. BaseRecyclerViewAdapterHelper源码解读(四) 上拉加载更多

    上拉加载 上拉加载无需监听滑动事件,可自定义加载布局,显示异常提示,自定义异常提示. 此篇文章为BaseRecyclerViewAdapterHelper源码解读第四篇,开源库地址,如果没有看过之前3 ...

最新文章

  1. how Lordec maps the long reads to DeBruijn Graph
  2. Object-C-block
  3. 改变层级_3DMAX基础,可编辑多边形层级介绍及概念
  4. OpenCV环境搭建(一)
  5. 小程序showModal 可配置文字
  6. IntelliJ IDEA 2014 付费版 免费版比较
  7. sip协议的功能及其应用
  8. Trie树的C++实现
  9. linux系统连接实验室服务器步骤详解
  10. mfc入门程序之简单的计算器
  11. TOM163vip邮箱靓号注册,域名邮箱如何注册
  12. 又是一年深秋时--西湖枫叶随拍
  13. 安卓手机格式化怎么弄_安卓手机怎么格式化
  14. html全屏ipad顶部状态栏,iPad横屏和竖屏界面尺寸设计规范【最全】
  15. Hive beeline常用操作
  16. nodejs+vue高校教室管理系统
  17. SpringBoot异常处理回滚事务详解(自动回滚、手动回滚、部分回滚)
  18. jeesite实战(二)——jeesite工具生成基本的页面
  19. CSR86XX ROM版 操作EEPROM,FLASH的操作流程
  20. GBase 8a Python Connector使用排雷

热门文章

  1. CF1399E1 Weights Division (easy version)
  2. [NOIp2017 DG Day 2 T1] 奶酪
  3. oracle游标作为out参数,oracle 存储过程 带游标作为OUT参数输出
  4. npm 更新_npm 的安全困扰:仅有 9.27% 的 npm 开发者使用 2FA
  5. python提示line3_Python小技巧:Python3中利用tab键进行代码提示-阿里云开发者社区...
  6. sync是同步还是非同步_高速AD项目学习笔记——实现sync同步的经验
  7. python dataframe 取每行的最大值_在pandas DataFrame中查找列的值最大的行
  8. mysql集群系统_轻松构建Mysql高可用集群系统
  9. 动态规划实战11 leetcode-64. Longest String Chain
  10. python函数进阶小结_Python 函数3000字使用总结