小白也能看懂SpringMVC源码

本文深入剖析SpringMVC的工作原理,从初始化阶段到服务阶段,详细解读DispatcherServlet的运行流程,包括初始化参数处理、请求处理、Handler映射、Adapter选择、请求执行及视图渲染等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SpringMVC源码解析

我们都知道SpringMVC的请求流程是这个样子的:
流程图
而在控制器端,最主要的就是DispatcherServlet,我们来看看它的由来:
继承关系
从图中,我们可以轻而易举的发现:DispatcherServlet说到底就是一个Servlet。说到Servlet,我们就得先来说一说,Servlet的生命周期。

Servlet生命周期:首先加载servlet的class,实例化servlet,然后初始化servlet调用init()的方法,接着调用服务的service的方法处理doGet和doPost方法,最后是我们关闭容器的时候调用destroy 销毁方法。

1. 初始化阶段

类加载及实例化servlet,我们就不说了,直接来说一说init()方法。
在这里插入图片描述
HttpServletBean继承了HttpServlet,重写了init()方法
在这里插入图片描述
我们一起来看一下这个方法。首先进行了debug日志打印,这一步没啥说的。
来看try{}cathch{}里边的方法。

在刚才的代码中,我们发现有一个属性叫做requiredProperties,这个是用来存放参数的,之后传给servlet用来初始化。

PropertyValues pvs 
	= new ServletConfigPropertyValues(
				getServletConfig(), this.requiredProperties);

这一步加载了我们的web.xml里的参数,来看我的示例。我的web.xml是这样的。
在这里插入图片描述
我们在这一步打个断点,看看这一步去到的参数是什么?
在这里插入图片描述
取到了我们的初始化参数contextConfigLocation。

接着往下走,

BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

在这里插入图片描述
↑ 这一步得到了我们的DispatcherServlet实例对象
在这里插入图片描述
↑ 这一步得到资源加载器
接下来依次是:为Servlet装配资源编辑器 → 初始化DispatcherServlet → 设置参数。

接下来的重中之重来了,initServletBean()方法,这个方法的注释这样写的“让子类做他们喜欢的任何初始化”。所以此时,我们的重点要放在子类上了,看看子类是如何进行初始化servletBean的。

我们回到FrameworkServlet这一层,来看一下这个类里边重写的initServletBean()方法。注释中这样写道“在任何bean属性之后调用
已经准备好了。创建此servlet的WebApplicationContext。”
在这里插入图片描述
在这个方法中,重要的方法就是initWebApplicationContext()
在这里插入图片描述
这个方法先去查找根节点上是否存在上下文实例

  • 如果存在则会进行赋值操作,将这个上下文实例作为返回的对象,在返回之前会判断这个上下文对象是否是配置类上下文对象,

    • 如果是则会创建配置类上下文对象,若这个对象不是存活状态,执行configureAndRefreshWebApplicationContext(),这个方法主要是装配上下文对象
  • 如果不存在,则会先执行findWebApplicationContext(),这个方法的主要功能是:在构造时没有注入上下文实例->查看是否有已在servlet上下文中注册。如果存在,则假定父上下文(如果有)已经设置,并且用户已执行任何初始化,如设置上下文ID

  • 上述操作后,如果没有注册上下文实例,则会执行createWebApplicationContext(rootContext)方法,意思就是没有我就创建一个本地的上下文对象

一起来看看这个方法createWebApplicationContext(),这个方法里最终会调用configureAndRefreshWebApplicationContext()
在这里插入图片描述
这个方法里会增加ApplicationListener来监听servlet事件。

  • 判断上下文对象是否是自动刷新,如果上下文不是具有刷新功能的configurableapplicationcontext,在构建时注入的支持或上下文已经
    刷新->在此处手动触发初始刷新

  • 判断是否应该将上下文发布为servletcontext属性,如果是则进行配置

  • 最后将上下文对象返回

至此,初始化工作告一段落,当然以上的分析只是一部分,还有好多方法没有分析到

2. service阶段

这一阶段,才是servlet实际工作的重要部分,接下来我们一起来看看
我们都知道servlet最主要的就是service()方法,DispatcherServlet继承自FrameworkServlet,FrameworkServlet间接的继承了HttpServlet,我们来看重写的service()方法。
在这里插入图片描述

这段代码很好理解,获取请求方式,如果是PATCH请求,则调用processRequest()方法,否则,调用父类的service()方法。补充说一下,请求方式现在一共有GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE八种,而PATCH是新增对于PUT的补充,所以在这个service()方法中会这样写,因为父类的service()中只包含7种方法,否则报错,所以说8中请求方式,最终都会调用processRequest()方法。

我们先来看一下父类的service()方法。
在这里插入图片描述
这个方法实际上就是对于不同的请求方式进行不同的处理,否则报501错误,究竟怎么处理的各种请求呢,doGet()、doPost()等等这些方法都是在子类里重写的,我们拿一个doGet()来看。
在这里插入图片描述
我们看到了主要的方法就是processRequest(),让我们继续跟进去
在这里插入图片描述
这里的发布事件,Spring在请求处理结束后会发布一个ServletRequestHandledEvent类型的事件,可以通过ApplicationListener接收。
这个方法前面和后面做的工作是保留现场,请求处理结束后恢复现场。真正处理请求的方法是doService。这个方法在DispatcherServlet中.
在这里插入图片描述
这个方法主要是把现在有的一些参数比如上下文对象加到Request中,然后转发到doDispatch方法去处理,终于到了最最最最关键的方法了。
在这里插入图片描述

checkMultipart()

首先来看这个方法,checkMultipart(),这个方法主要处理的就是我们常用的文件上传请求,如果是文件上传请求,将该请求封装为标准servletrequest请求。

getHandler()

接下来一个重要的方法getHandler(),
在这里插入图片描述
我们在这段代码中,可以看出,这个方法的返回值是HandlerExecutionChain类型,意思就是“处理程序执行链”,在这段代码中,核心的代码就是 AbstractHandlerMapping类中的getHandler(request)方法。在看这个方法之前,我们先来看一下handlerMappings,这段代码中循环的handlerMappings是什么时候有的值呢?里边的值又是什么呢?

这我们就要来看一下initWebApplicationContext()方法里的onRefresh()方法
在这里插入图片描述
initStrategies()这个方法里边进行很多组件的初始化工作,前三个时初始化解析器,点进去可以看到三个方法里边就是解析器的名字不一样,做的工作都是一样的,这里我就只贴关键代码了。

this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);

this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);

this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);

接下来,我们来看一下initHandlerMappings(context)方法,其实我们只看这个方法名和参数就可以知道,它是从上下文实例中,获取handlerMapping放到handlerMappings集合中。是不是像我们想的这样呢?
在这里插入图片描述
确实像我们想的那样,获取了上下文实例中的handlerMapping放到handlerMappings集合中,那我们就打个断点看看例子中handlerMappings集合中都有什么?
在这里插入图片描述
接着看
在这里插入图片描述
发现了什么?我们自己在配置文件写的接口被添加进去了
在这里插入图片描述
既然是已经被添加进去了,我们就接着之前的请求到达getHandler(request)方法来看。
在这里插入图片描述
这个里边有两个主要方法getHandlerInternal(request)、getHandlerExecutionChain(handler, request),先来看getHandlerInternal(request)
在这里插入图片描述
这个方法里边呢,又有两个重要方法getLookupPathForRequest和lookupHandlerMethod,说一下实现原理吧,其实RequestMappingHandlerMapping在初始化的时候已经将系统中所有的@RequestMapping注解解析了,放在一个Map里面。实现过程如下:
在这里插入图片描述
RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping实现了InitializingBean接口,在Bean设置完参数后会调用afterPropertiesSet方法,而它在这个方法里面做了初始化的工作。

另一个方法getHandlerExecutionChain(),就是默认实现只是使用给定的处理程序、处理程序映射的公共侦听器和任何与当前请求URL匹配的mappedInterceptor生成一个标准的handlerExecutionChain.

在这里插入图片描述
其实这个方法就是加了自定义拦截器

这样的话,getHandler()方法我们就分析的差不多了,接下来来看

getHandlerAdapter()

接下来来看getHandlerAdapter()方法
在这里插入图片描述
我们前边提过initHandlerAdapters(ApplicationContext context)已经初始化了handlerAdapters这个集合,所以这一步应该很好理解了.

handle()

我们直接来看AbstractHandlerMethodAdapter里的handle方法
在这里插入图片描述
handleInternal()实际上调用的是子类实现的方法
在这里插入图片描述
在这个里边主要的方法有两个checkAndPrepare()和invokeHandleMethod()

我们来看一下checkAndPrepare()做了什么工作
在这里插入图片描述
这个方法根据此生成器的设置检查并准备给定的请求和响应。检查支持的方法和所需的会话,并应用给定的缓存秒数,applyCacheSeconds()就是用来处理缓存时间戳

所以重要的还是这个方法invokeHandleMethod()
在这里插入图片描述
主要执行的方法是invokeAndHandle(),我们来看看
在这里插入图片描述
这个方法里主要的方法就是invokeForRequest(),我们跟进去看看
在这里插入图片描述
再来看这个方法里边的getMethodArgumentValues()
在这里插入图片描述
在这个方法里边的supportsParameter()方法,这个方法实际上调用了supportsParameter()方法,这个方法就是调用每个Resolver的support方法,看看是否能够进行类型转换。类型处理器在argumentResolvers.resolveArgument处理完参数后,会把request的参数转成一个Object[]的列表返回,就是Controller中方法的参数列表。

再来回头看invokeForRequest()中的invoke()方法
在这里插入图片描述
实际上就是带着所有参数,去执行方法

视图及数据的返回

applyDefaultViewName(request, mv)方法主要是如果没有视图名,则设置视图的名字,mappedHandler.applyPostHandle(processedRequest, response, mv)主要是拦截器的操作

来看下一个方法processDispatchResult()
在这里插入图片描述
在这里边主要调用了render方法

在这里插入图片描述
方法里的resolveViewName()
在这里插入图片描述
至此我们获得了ModelAndView

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值