从上帝视角认识SpringMVC预览
创始人
2024-03-12 08:28:45
0

前言

SpringMVC提供了很多可拓展的组件,例如:参数解析器、拦截器、异常处理器等等。但是如果想要理解/找到这些组件工作的位置/时机,很多时候总是容易迷失在其层层调用的源码之中。因此才想从上帝视角来剖析它。而所谓上帝视角,就是回到SpringMVC的设计本身,来理解他。

DispatcherServlet

相信大家都认识这东西,作用啥的也就不多说了。但请先暂时记住这一点: 他是javax.servlet.Servlet。

环境准备

WebApplicationContext

什么是上下文

还记得以前做阅读理解题目的时候吗?老师经常说的一句话是:联系上下文。又比如,你跟淘宝客服聊天,有时候我们总是先把商品发给对方,然后再说这个有没有其他颜色之类的。而这些推动事情继续发展的“背景知识”就可以叫做上下文。
回到Spring,像环境配置、bean对象在哪里、事件发布等等,都属于上下文信息。即使,不是直接的进行参与,至少也要能够“联系”到对应的人来处理。例如,BeanFactory。如果从这个角度看的话,还能这样理解,他就是一个信息机构,有点像情报机构。你想要的东西他都能给你找来。实际上,我们在工作中,临时接入某个事情的时候,都需要进行交接/学习“上下文”。在了解事情的背景知识之后,我们才能进一步开展工作。
在这里插入图片描述
不过,由于Spring的上下文,是整个应用运行的上下文,因此Spring的上下文需要具备的背景知识比较庞杂,理解起来不容易。但我们可以从他的继承关系来理解他的能力。这里就不扩展了。

父子上下文

在这里插入图片描述
SpringMVC把上下文分为Servlet容器的上下文,和根应用上下文。这点可能有不少人知道。但是,为什么要这样分呢?先看下两个上下文的职责分工。

  • Servlet上下文包含控制器、试图解析器,以及其他web相关的bean
  • Root上下文,则包含中间层服务,数据源等。

可能有同学会问,为什么要多此一举呢?只使用一个上下文不行吗?还更容易理解。但是,有的情况下,我们的web应用可能不止提供http服务。例如:定时任务。定时任务跟Servlet有关系吗?
从设计原则上说,这种划分可能也是基于最少知识原则,降低系统的耦合,也为其可维护性埋下伏笔。简而言之,web上下文只管web相关的bean,应用上下文管其他与web无关的bean。

Root ApplicationContext怎么加载呢?

DispatcherServlet通过自己的上下文来提供web服务。并且该web上下文还有个父亲。我们很容易想到在DispatcherServlet创建/初始化时,创建一个web上下文并扫描相关的webBean。但是Root ApplicationContext增加加载呢?怎么衔接起来呢?
DispatcherServlet不就是一个servlet吗?通过ServletContext传递不就好了。但是什么时候创建呢?于是我们就需要到tomcat里面来了。
tomcat启动的时候,也有自己的上下文:ServletContext。这个时候,可能有同学要晕车。。其实,你回过头再理解一下上下文,或许就好了。这就类似于我们现实生活中,做不同的事情,所需要了解的背景知识不一样。如果你用SpringCloud,还有个Bootstrap ApplicationContex,可以自己理解一下,它又是干啥的。
当ServletContext初始化完成后,就会发布一个ServletContextEvent。这时只要我们实现ServletContextListener,即可收到通知。于是,我们便可借此机会初始化Root ApplicationContext,并放到ServletContext里面。
在这里插入图片描述

DispatcherServlet启动

DispatcherServlet有一个Web上下文,并且由他管理着所有的web相关的bean。这意味着,在DispatcherServlet具备提供服务的能力之前,我们需要先将这个上下文准备好。而之前说的,只是Root Application。
如果是你,你会怎么做呢?很容易想到就是在创建DispatcherServlet的时候对上下文进行初始化。只不过我们的DispatcherServlet本质上是Servlet,因此只需要跟随Servlet的生命周期函数就好。所以选择在javax.servlet.Servlet#init方法进行初始化。
实际的实现在:org.springframework.web.servlet.FrameworkServlet#initServletBean
要找到他的实际创建和刷新上下文,还是有点绕的,调用链路贴一下:
在这里插入图片描述
而org.springframework.web.servlet.DispatcherServlet#initStrategies方法就是从web上下文中获取DispatcherServlet所依赖的相关的webBean.

    protected void initStrategies(ApplicationContext context) {this.initMultipartResolver(context);this.initLocaleResolver(context);this.initThemeResolver(context);this.initHandlerMappings(context);this.initHandlerAdapters(context);this.initHandlerExceptionResolvers(context);this.initRequestToViewNameTranslator(context);this.initViewResolvers(context);this.initFlashMapManager(context);}

DispatcherServlet内部的九大组件

从上面的初始化代码来看,有如下组件;
MultipartResolver:文件上传处理器
LocaleResolver:多语言解析器
ThemeResolver: 主题解析器,简单来说就是页面的整体样式。可参考文章
HandlerMapping: 处理器映射器,找到对应的处理器就靠他了
HandlerAdapter:处理器适配器
HandlerExceptionResolver:处理器异常解析器
RequestToViewNameTranslator:将请求翻译成视图名字的解释器
ViewResolver:视图解析器
FlashMapManager:FlashMap管理器。用于重定向请求的。FlashMap本身是一个Map,把跳转前的参数保存在FlashMap中。然后再将其作为Request的session属性进行设置。

从我们经常使用的@RequestMapping的方式声明一个Http接口的角度看,以上组件并不是完全必要的。因此这里不会讨论无关的组件。在这里提醒一下,spring的这些工具可能不止一个实现,不同的实现对应不同的使用场景。例如HandlerMapping的BeanNameUrlHandlerMapping,把bean的名字作为请求uri映射给该bean来处理请求。RequestMappingHandlerMapping则是通过@RequestMapping进行uri地址映射的。本次我们讨论的是与@RequestMapping相关的。

RequestMappingHandlerMapping需要什么信息?
  1. 他需要知道哪些对象有@RequestMapping注解,才能找到uri对应的处理方法。这意味着他需要对所有的bean进行遍历。

    因此需要解析@RequestMapping,包括这些信息:可以处理的uri,以及各种限定条件。例如:该注解可以指定处理POST请求

  2. 他需要按照方法参数来进行参数解析,以便调用目标处理方法

    因此他需要参数转换服务。例如:字符串转Date

RequestMappingHandlerMapping初始化

顺着上面的九大组件的初始化方法找:

> org.springframework.web.servlet.DispatcherServlet#initHandlerMappings> org.springframework.web.servlet.DispatcherServlet#getDefaultStrategies

如果没有配置HandlerMapping,那么就会找到org.springframework.web.servlet包下的DispatcherServlet.properties,从这里面加载默认的HandlerMapping.
但这里还没完,因为RequestMappingHandlerMapping实现了InitializingBean接口。而实际上到这里,他也只是创建了这个对象,并没有对我们之前说的各种属性进行初始化。
在afterPropertiesSet方法会调用到下面这个方法

    protected void initHandlerMethods() {// 遍历bean工厂中所有beanNamefor (String beanName : getCandidateBeanNames()) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// 处理候选bean:他会检查bean是否为Controller,如果是,还会注册到MappingRegistry,也即Mapping注册中心。所谓Mapping就是uri->handlerprocessCandidateBean(beanName);}}handlerMethodsInitialized(getHandlerMethods());}

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...