深入解读springmvc--1,摸清DispatcherServlet

2023-10-18 14:49

本文主要是介绍深入解读springmvc--1,摸清DispatcherServlet,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一,前言

很惭愧最近没更新博客,闲的慌废寝忘食地看了几本小说,还好一周时间就看吐了,上周空闲之余粗略看了下Spring MVC的源码,先把上周的一些思路整理一下,走一下springmvc的流程。

打算写一个springmvc系列的博客,从源码解读,到自己实现一个处理请求,生成视图和模型并且可以进行视图的解析的A货MVC框架。DispatcherServlet是整个SpringMvc框架的核心,是整个web应用程序的入口点,捕获了所有的请求,进行分析找到相应的控制器和方法来处理这个请求,然后生成模型和视图,最后由视图来进行渲染自身。

整篇博客分为三个部分:

  • 了解Java Servlet
  • DispatcherServlet的继承关系
  • DispatcherServlet初始化做的每一步的工作

下一篇博客会继续分析DispatcherServlet:

  • mvc处理Http请求的具体流程
  • 自己实现一个A货DispatcherServlet

二,了解Java Servlet

什么是Servlet呢,servlet是一个java类,是服务器端运行的一个小程序,并且看可以通过请求-响应编程模型来访问的服务器内存中的程序。

java实现servlet的三种方式:

1,java定义好了servlet的接口,我们直接实现其即可:

enter description here

五个方法:

  • init: servlet被Servlet容器实例化的时候就调用,如果init方法抛出ServletException或者是没有在规定时间内return那么容器就会直接忽视这个Servlet
  • getServletConfig: 获得Servlet初始化的配置
  • service: 被Servlet容器调用进行请求的处理
  • getServletInfo: 获取Servlet的信息
  • destroy: 当Servlet服务完成之后由容器调用,进行资源的释放等操作

实现了Servlet接口,就可以放在Servlet容器中,在web.xml配置中配置Servlet和其对应的响应路径即可。给出一个简单的例子:

项目结构:

enter description here

web.xml配置:

1
2
3
4
5
6
7
8
9
<servlet><servlet-name>xihao</servlet-name><servlet-class>com.test.xihao.XihaoWeb</servlet-class></servlet><servlet-mapping><servlet-name>xihao</servlet-name><url-pattern>/xihao</url-pattern></servlet-mapping>

XihaoWeb就是一个简单实现了Servlet接口的类,然后把整个项目部署到tomcat中即可。

2,继承GenericServlet

GenericServlet是一个通用的,不依赖具体协议的Servlet,实现了Servlet,ServletConfig接口,还实现了log方法,只需要继承GenericServlet,实现一个抽象方法service即可。

然后在web.xml中声明其映射。

3,继承HttpServlet

具体的Http协议的实现,继承了GenericServlet,最通用的一种实现servlet的方式就是直接继承HttpServlet然后重写其中的:

  • doGet
  • doPost
  • doPut
  • doDelete
  • init
  • getServletInfo
    等方法即可。

三,DispatcherServlet的继承关系

springmvc源码错综复杂,就是一个庞大的蜘蛛网,我们的思路一定要清晰,先从DispatcherServlet的继承关系下手,看下其继承关系:

enter description here

HttpServletBean

HttpServletBean 实现了EnvironmentCapable,EnvironmentAware接口,可以自定义web运行的环境,继承HttpServlet,主要需要注意的是初始化的时候完成了web容器的依赖注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public final void init() throws ServletException {if (logger.isDebugEnabled()) {logger.debug("Initializing servlet '" + getServletName() + "'");}// Set bean properties from init parameters.PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);//从ServletContext中获取Bean属性参数,然后进行web容器的依赖注入ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);}throw ex;}}//子类自己实现ServletBean具体的初始化initServletBean();if (logger.isDebugEnabled()) {logger.debug("Servlet '" + getServletName() + "' configured successfully");}}

FrameworkServlet

SpringMvc框架的基层Servlet,提供了在bean层面上与Spring应用上下文的集成,主要实现了三个功能:

  • 基于每个Servlet的命名空间中的beans,为每个Servlet实例化一个WebApplicationContext,并对其进行管理
  • 在处理请求上发布事件,不管请求是否成功
  • 完成了Servlet上下文的建立之后,子类实现onRefresh方法,完成IOC容器的初始化

子类必须覆盖其doService方法实现具体的请求处理逻辑。

DispatcherServlet

作为一个前端控制器,所有的web请求都需要通过它进行处理,进行转发,匹配,数据处理后,并由页面进行处理。是SpringMvc的核心。

主要实现的功能可以分为两个部分:

  • 初始化部分,主要在initWebApplicationContext中,对web模块需要的bean进行了初始化
  • 对Http请求进行响应,主要在doDispatch中实现

四,DispatcherServlet的执行链

DispatcherServle怎么样处理Http请求的呢,还有初始化的时候具体做了些什么呢?

看下两条主线:

DispatcherServlet自身IOC容器的初始化:

enter description here

处理Http请求:

enter description here

1,DISPATCHERSERVLET的初始化

DispatcherServlet自己持有一个IOC容器,整个web应用有一个根上下文,在ContextLoaderListener初始化的时候被建立了,并且被设置到WebApplicationContextUtils中去。

然后可以理解为每个Servlet持有一个子上下文,DispatcherServlet的持有的WebApplicationContext以web应用的根上下文作为自己的parent(这个过程在FrameworkServlet中实现)。需要知道的是,在向IOC容器getBean的时候,首先会到双亲上下文中寻找,也就是说,整个web项目的根上下文定义的bean是可以被所有的子上下文共享的。

具体看下FrameworkServlet中的initServletBean方法,以及一些注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
protected WebApplicationContext initWebApplicationContext() {//获取根上下文,使用这个作为当前mvc上下文的双亲上下文WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null;if (this.webApplicationContext != null) {//构造函数时候已经注入了web应用上下文wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {//没有双亲上下文的话,设置上下文,可能为nullcwac.setParent(rootContext);}//对子上下文进行基本配置和最终调用IOC容器的refresh方法configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {// 没有的话 就去ServletContext中找,找到的话 默认按照根上下文已经被设置并且用户已经执行了初始化来处理wac = findWebApplicationContext();}if (wac == null) {// 最后还是没有找到的话,创建一个wac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {// 还没有进行刷新事件的话// 如果创建的上下文不支持刷新或者是构造器注入的上下文已经被调用过refresh方法,在这里手动刷新onRefresh(wac);}if (this.publishContext) {//把生成的子上下文设置到SevletContext中去String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);if (this.logger.isDebugEnabled()) {this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +"' as ServletContext attribute with name [" + attrName + "]");}}return wac;}

FrameworkServlet中的onRefresh方法是空的,是一个模板留给子类实现。然后DispatcherServlet具体实现了onRefresh方法,调用自身的initStrategies方法,看下其具体实现:

enter description here

initStrategies(ApplicationContext context)就是mvc框架的各种实现元素的初始化,包括国际化的LocaleResolver,配置请求和controller映射的HandlerMappings等。

上面的流程还有最后的两步没有打通,哪两步呢:

  • configureAndRefreshWebApplicationContext方法和createWebApplicationContext的具体实现
  • 最后的以initHandlerMapping方法为例,最后的mvc组件的具体实例化又是怎么样的呢?

两个方法解析:

首先看下createWebApplicationContext:

enter description here

设置一些基本属性之后还是调用的configureAndRefreshWebApplicationContext方法,看下configureAndRefreshWebApplicationContext:

enter description here

其实就是设置一些基本的参数之后,然后进行容器的所有bean的实例化。

initHandlerMapping具体解析:

enter description here

看下默认的配置,DispatcherServlet.properties:

enter description here

可以看到默认的HandlerMappings的具体实现是BeanNameUrlHandlerMapping和RequestMappingHandlerMapping。

  • BeanNameUrlHandlerMapping 基于bean名字来匹配映射,举个例子就是有个/userName请求,会查找controller的name属性为”/userName”的controller,支持模糊匹配,”/user*”也可以匹配到”/userName”的controller。
  • RequestMappingHandlerMapping 应该是最常用的一个解析器了吧,请求直接映射到@RequestMapping注解的类和方法上面。

五,结尾

写到这里已经是深夜的00:30了,这种源码阅读的操作简直就是在啃书一样,太过于晦涩了,错综复杂的调用关系以及层层继承下来,需要时刻保持头脑的高度清晰和集中。

但是现在,整个DispatcherServlet初始化的步骤已经很明朗,流程贯通的话,还是有一点点的成就感的,清楚了各个步骤之后,我们就可以自己定制一个DispatcherServlet了,也可以按照自己的需求进行框架的深度定制,这些操作留到下一篇博客,给自己找一些成就感哈哈。

这篇关于深入解读springmvc--1,摸清DispatcherServlet的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/233252

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2