Spring MVC 源码分析之 DispatcherServlet

2024-01-11 00:32

本文主要是介绍Spring MVC 源码分析之 DispatcherServlet,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、概述

二、类图

三、初始化过程

四、请求过程

五、总结


一、概述

MVC大家比较熟悉

  • M即model,是业务处理层,与我们开发中的(service、dao、model)等对应起来;
  • V即view,是视图层,以前jsp、freemarker、velocity等,现在都是前后端分离了。使用@ResponseBody注解把Controller方法返回的对象通过转换器转换成指定的格式(如json/xml/protobuf)后,再写入到Response对象的body区,不再走视图解析器,把渲染到工作交给前端去做。
  • C,即controller,控制器,可以分为前端控制器(负责请求的分发,即DispatcherServlet)、映射处理器(uri与处理方法的映射,即HandlerMapping)、业务控制器(即我们的controller层)、视图解析器(即ViewResolver)。

二、类图

如上图类的继承关系可知,DispatcherServlet就是一个Servlet.

三、初始化过程

了解Servlet的都知道在Servlet中主要的方法有:

  • init  初始化方法
  • service 用于处理请求的方法
  • destroy servlet的销毁方法

3.1、HttpServletBean类初始化方法 init

public final void init() throws ServletException {// Set bean properties from init parameters.PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {//将Servlet中配置的参数封装到pvs变量中,requiredProperties为必须参数,如果没//配置将报异常BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);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;}}// Let subclasses do whatever initialization they like.// 模板方法,子类初始化入口方法,在子类中可以做一些具体的实现方法initServletBean();}

  3.2、在 FrameworkServlet 中实现了 initServletBean 方法

@Overrideprotected final void initServletBean() throws ServletException {// 略 .....try {//主要方法,主要实现了 初始化 WebApplicationContext this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException | RuntimeException ex) {logger.error("Context initialization failed", ex);throw ex;}// 略 .....}}

3.3、initWebApplicationContext 方法

protected WebApplicationContext initWebApplicationContext() {//  略...if (!this.refreshEventReceived) {// Either the context is not a ConfigurableApplicationContext with refresh// support or the context injected at construction time had already been// refreshed -> trigger initial onRefresh manually here.synchronized (this.onRefreshMonitor) {onRefresh(wac);  //重点方法}}if (this.publishContext) {// Publish the context as a servlet context attribute.String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;}

 

initWebApplicationContext方法做了三件事:

  1.    获取spring的根容器rootContext。
  2.    设置webApplicationContext并根据情况调用onRefresh方法。
  3.    将webApplicationContext设置到ServletContext中。

3.4、DispatcherServlet中的 onRefresh 方法

@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}/*** Initialize the strategy objects that this servlet uses.* <p>May be overridden in subclasses in order to initialize further strategy objects.*/protected void initStrategies(ApplicationContext context) {//初始化多媒体解析器,这个不分析initMultipartResolver(context);//初始化国际化解析器,这个不进行分析initLocaleResolver(context);//初始化主题解析器,基本不用,不进行分析initThemeResolver(context);//初始化映射器initHandlerMappings(context);//初始化适配器initHandlerAdapters(context);//初始化异常解析器initHandlerExceptionResolvers(context);//初始化视图名转换器,不进行分析initRequestToViewNameTranslator(context);//初始化视图解析器,前端分离后,用得少了,也分析下initViewResolvers(context);//初始化FlashMapManager,只知道是重定向时用来保存数据用的,没有使用过,这里不进行分析initFlashMapManager(context);}

下面贴的代码的几个初始化代码的逻辑其实是一样的,分为三步,以initHandlerMappings进行说明

  1.   判断是否寻找所有容器中实现HandlerMapping接口的,是则寻找所有容器中实现了HandlerMapping接口的对象,设置到handlerMappings对象中并排序(平常开发中走的都是这个分支)
  2.   否的话,只找寻到前容器实现了HandlerMapping接口的对象
  3.   如果上面查找后为空的话,则加载配置文件中的类并实例化,再设置到handlerMappings中。注意的是:这个配置文件位置是:org/springframework/web/servlet/DispatcherServlet.properties

至此,DispatcherServlet的初始化工作已经完成。

四、请求过程

上文介绍过 Servlet中处理请求的为 service 方法。下面先看service方法的处理逻辑

4.1、HttpServlet中的 service方法

 protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {String method = req.getMethod();if (method.equals(METHOD_GET)) {long lastModified = getLastModified(req);if (lastModified == -1) {// servlet doesn't support if-modified-since, no reason// to go through further expensive logicdoGet(req, resp);} else {long ifModifiedSince;try {ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);} catch (IllegalArgumentException iae) {// Invalid date header - proceed as if none was setifModifiedSince = -1;}if (ifModifiedSince < (lastModified / 1000 * 1000)) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) {long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) {doPost(req, resp);} else if (method.equals(METHOD_PUT)) {doPut(req, resp);} else if (method.equals(METHOD_DELETE)) {doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) {doOptions(req,resp);} else if (method.equals(METHOD_TRACE)) {doTrace(req,resp);} else {//// Note that this means NO servlet supports whatever// method was requested, anywhere on this server.//String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}}

从源码中可得知 HttpServlet中 service 方法根据请求方法,把请求具体交给了 doGet、doPost、doPut等方法来处理

4.2、FrameworkServlet 中的 service 方法

在FrameworkServlet中重写了 service 方法同时也重写了 doGet、doPost,doPut等方法,源码如下所示

@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (httpMethod == HttpMethod.PATCH || httpMethod == null) {processRequest(request, response);}else {super.service(request, response);}}@Overrideprotected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 主要通过此方法实现请求的分发processRequest(request, response);}

有源码可知,在doGet 方法中有调用了 processRequest方法,其他doXX中同样调用了此方法,接下来看看 此方法。

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {long startTime = System.currentTimeMillis();Throwable failureCause = null;// 获取LocaleContextHolder 中保存的LocalContextLocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();//获取当前请求的LocaleContextLocaleContext localeContext = buildLocaleContext(request);//获取RequestContextHolder中保存的RequestAttributesRequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();//获取当前请求的ServletRequestAttributesServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);// 略 .....//将当前请求的LocaleContext和ServletRequestAttributes保存到//LocaleConextHolder和RequestContextHolder中initContextHolders(request, localeContext, requestAttributes);try {//模板方法,在子类中实现doService(request, response);}catch (ServletException | IOException ex) {//略 ....}catch (Throwable ex) {//略 ....}finally {//恢复之前的LocaleContext和ServletRequestAttributes到//LocaleConextHolder和RequestContextHolder中resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}//发布ServletRequestHandledEvent消息publishRequestHandledEvent(request, response, startTime, failureCause);}}

  说明: 由于在请求时把LocaleContext和ServletRequestAttributes保存到了LocaleContextHolder和RequestContextHolder中,这两个类都是在ThreadLocal中所有在后续的方法中可以根据通过这两个类获取LocalContext和ServletRequestAttributes,从而获取HttpServletRequest、HttpServletResponse和HttpSession。

 4.3、DispatcherServlet中实现了 doService方法,

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {//省略 部分代码 .....try {//真正处理 请求转发的方法doDispatch(request, response);}finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}}

有源码可知 doService方法中主要是调用了 doDispatch方法,接下来看看此方法的实现

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {//检测是否为上传请求processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.//根据 request 查找对应的 Handler mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.//根据 Handler查找执行此Handler的 handler适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}//执行拦截器的前置方法if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.//实际调用 Handlermv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);//执行拦截器中的 postHandle 方法mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}//处理返回结果。包括处理异常、渲染页面、发出完成通知调用拦截器的 afterComletion方法processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

五、总结

 本篇文章主要是分析了DispatcherServlet 的初始化和请求的响应过程,接下来将会分析 Handler的查找过程。

 

这篇关于Spring MVC 源码分析之 DispatcherServlet的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

springboot项目中整合高德地图的实践

《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一:高德开放平台的使用二:创建数据库(我是用的是mysql)三:Springboot所需的依赖(根据你的需求再

spring中的ImportSelector接口示例详解

《spring中的ImportSelector接口示例详解》Spring的ImportSelector接口用于动态选择配置类,实现条件化和模块化配置,关键方法selectImports根据注解信息返回... 目录一、核心作用二、关键方法三、扩展功能四、使用示例五、工作原理六、应用场景七、自定义实现Impor

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.