SpringMVC工作原理及DispatcherServlet源码解析

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

一、SpringMVC整体流程

先来看看SpringMVC的工作流程图:

下面来看看每一步都是在做什么:

第一步:用户发起request请求,请求至DispatcherServlet前端控制器。

第二步:DispatcherServlet前端控制器请求HandlerMapping处理器映射器查找Handler。

              DispatcherServlet:前端控制器,相当于中央调度器,各各组件都和前端控制器进行交互,降低了各组件之间耦合度。

第三步:HandlerMapping处理器映射器,根据url及一些配置规则(xml配置、注解配置)查找Handler,将Handler返回给                          DispatcherServlet前端控制器。

第四步:DispatcherServlet前端控制器调用适配器执行Handler,有了适配器通过适配器去扩展对不同Handler执行方式(比如:                原始servlet开发,注解开发)

第五步:适配器执行Handler。Handler是后端控制器,当成模型。

第六步:Handler执行完成返回ModelAndView

              ModelAndView是springmvc的一个对象,对Modelview进行封装。

第七步:适配器将ModelAndView返回给DispatcherServlet

第八步:DispatcherServlet调用视图解析器进行视图解析,解析后生成view。视图解析器根据逻辑视图名解析出真正的视图。

              View:springmvc视图封装对象,提供了很多view,比如:jsp、freemarker、pdf、excel。

第九步:ViewResolver视图解析器给前端控制器返回view

第十步:DispatcherServlet调用view的渲染视图的方法,将模型数据填充到request域 。

第十一步:DispatcherServlet向用户响应结果(jsp页面、json数据。)

在这里面有几个组件要说一下:

  • DispatcherServlet:前端控制器,由springmvc提供
  • HandlerMappting:处理器映射器,由springmvc提供
  • HandlerAdapter:处理器适配器,由springmvc提供
  • Handler:处理器,需要程序员开发
  • ViewResolver:视图解析器,由springmvc提供
  • View:真正视图页面需要由程序编写

 由上面springMVC的工作流程图我们可以知道,其实springMVC的核心就是DispatcherServlet,所以接下来主要就是通过源码来看看DispatcherServlet是怎么工作的。

FROM 《SpringMVC - 运行流程图及原理分析》

流程示意图

代码序列图


FROM 《看透 Spring MVC:源代码分析与实践》 P123

流程示意图

 

二、服务流程

一般而言,Servlet有一个服务方法 doService来为HTTP请求提供服务(如参照前面的servlet源码解析), DispatcherServlet也是如此,它的 doService 方法如下面代码所示:

//将DispatcherServlet特定的请求转发给doDispatch处理
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {if (logger.isDebugEnabled()) {String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");}// 快照处理,使用快照可以更快响应用户请求Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<String, Object>();Enumeration<?> attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// 设置Web IOC容器request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());//设置国际化request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);//主题属性request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);//主题源属性request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);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先对HTTP请求进行一些处理,然后设置一些属性,最后所有的流程都集中在了 doDispatch 方法中。所以接下来我们继续看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);// 获得匹配的执行链,决定处理请求的handlermappedHandler = getHandler(processedRequest);//没有处理器错误if (mappedHandler == null || mappedHandler.getHandler() == null) {noHandlerFound(processedRequest, response);return;}// 找到对应的处理器适配器(HandlerAdapter)HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.///判断是 http 的 get 方法还是 post 方法或其他String method = request.getMethod();//如果是GET方法的处理boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (logger.isDebugEnabled()) {logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}//执行拦截器的事前方法,如果返回 false,则流程结束if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.//执行处理器,返回 ModelAndViewmv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}//如果视图为空,给予设置默认视图的名称applyDefaultViewName(processedRequest, mv);//执行处理器拦截器的事后方法mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {//记录异常dispatchException = ex;}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Throwable ex) {//记录异常dispatchException = new NestedServletException(”Handler dispatch failed ”, err) ;}//处理请求结果,显然这里已经通过处理器得到最后的结果和视图 //如果是逻辑视图,则解析名称,否则就不解析,最后渲染视图processDispatchResult(processedRequest, response,mappedHandler, mv, dispatchException);}catch (Exception ex) {//异常处理,拦截器完成方法triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {//错误处理triggerAfterCompletionWithError(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);}}}}

通过源码可以看出其流程是 :
(l)通过请求找到对应的执行链,执行链包含了拦截器和开发者控制器。

(2)通过处理器找到对应的适配器。

(3)执行拦截器的事前方法,如果返回 false,则流程结束,不再处理 。

(4)通过适配器运行处理器,然后返回模型和视图。

(5)如果视图没有名称 ,则 给出默认的视图名称。

(6)执行拦截器的事后方法。

(7)处理分发请求得到的数据模型和视图的渲染。

三、处理器和执行器

在上面,我们已经了解了一个请求到达springMVC后,DispatcherServlet是怎么处理这个请求的,接下来就是看看处理器和拦截器,即DispatcherServlet的getHandler方法:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {for (HandlerMapping hm : this.handlerMappings) {if (logger.isTraceEnabled()) {logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");}HandlerExecutionChain handler = hm.getHandler(request);if (handler != null) {return handler;}}return null;}

在启动期间, SpringMVC已经初始化了处理器映射--HandlerMappings,所以这里先根据请求找到对应的 HandlerMapping,找到 HandlerMapping 后,就把相关处理的内容转化成一个 HandlerExecutionChain 对象。

接下来就来看看HandlerExecutionChain的结构是什么样的:

public class HandlerExecutionChain {private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);//处理器,自己写的控制器(或其方法)private final Object handler;//拦截器数组private HandlerInterceptor[] interceptors;//拦截器列表private List<HandlerInterceptor> interceptorList;//当前拦截器下标,当使用数组时有效private int interceptorIndex = -1;...
}

由源码可以看出,主要是定义一些拦截器相关的属性,可以在进入控制器之前 , 运行对应的拦截器,这样就可以在进入处理器前做一些逻辑了。如果在做电商系统的时候,用户可以再未登录状态浏览商品,并加入购物车,但是如果要结算的时候,这个时候就要拦截请求,要求用户登录。这个时候就可以通过拦截器实现。接下来就来看看拦截器Handlerlnterceptor的相关方法:

public interface HandlerInterceptor {boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;

依据 doDispatch 方法,当preHandle方法返回为 true 的时候才会继续运行,否则就会结束流程,而在数据模型渲染视图之前调用 postHandle方法, 在流程的 finally语 句中还会调用 afterCompletion方法, 这就是处理器拦截器的内容。如果要自己做一个拦截器的话,可以实现Handlerlnterceptor接口,然后重写这三个方法,你可以在这三个方法里面加入相应的业务逻辑进行处理。

四、视图渲染

在第二点的时候,我们知道processDispatchResult对模型和视图的处理,接下来我们来看看SpringMVC是怎么实现视图渲染。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {boolean errorView = false;//处理器发生异常if (exception != null) {//视图和模型定义方面的异常if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}//处理器的异常else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}//处理器是否返回视图和模型if (mv != null && !mv.wasCleared()) {//渲染视图render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {//视图为 null,或者已经被处理过if (logger.isDebugEnabled()) {logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +"': assuming HandlerAdapter completed request handling");}}//是否存在并发处理if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}//完成方法if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, null);}}

由上面的源码可以看出,首先是先判断是否异常,如果有异常就再进一步处理是什么异常,如果没有异常就通过render方法进行进一步处理,相关逻辑也在注释中表明了,接下来就来看看render这个方法:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.
//国际化Locale locale = this.localeResolver.resolveLocale(request);response.setLocale(locale);View view;
//是否是逻辑视图,如果是需要转化为真实路径下的视图if (mv.isReference()) {// We need to resolve the view name.
//转化逻辑视图为真实视图view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +"' in servlet with name '" + getServletName() + "'");}}
//非逻辑视图else {// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}// Delegate to the View object for rendering.if (logger.isDebugEnabled()) {logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");}try {
//渲染视图, view 为视圈, mv.getModelInternal ()为数据模型view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +getServletName() + "'", ex);}throw ex;}}

首先是国际化的设置,然后判断是否是逻辑视图,如果是就转化它为 真实视图,或者 直接获取视图即可,最后进入视图的 render方法,将模型和视图中的数据模型传递到该方 法中去,这样就完成了视图的渲染,展示给用户。

这篇关于SpringMVC工作原理及DispatcherServlet源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环