从源码角度了解SpringMVC的执行流程

  SpringMVC的执行流程网上有很多帖子都有讲解,流程图和文字描述都很详细,但是你如果没有通过具体源码自己走一遍流程,其实只是死记硬背。所以想开个帖子从源码角度再梳理一遍SpringMVC的执行流程,加深印象。

SpringMVC介绍

  SpringMVC采用的是前端控制器(Front Controller) + 各个业务处理器(Controller)来处理请求的。前端控制器来响应所有请求,通过一定的调度规则找到具体负责处理的业务处理器,并将请求委派给具体的业务处理器去执行业务逻辑,业务处理器返回给前端控制器模型数据model,最后前端控制器将model交给视图View进行渲染。

源码分析思路

  看源码的同学可能往往会陷入一个怪圈,刚开始看可能还能看懂,等到一层一层点进去会越来越晕,让自己陷入了太多的细节中,而这些细节其实对主要流程并没有多大影响,然后就埋头研究。之后不得不又从头开始看,又让自己陷入了另一个细节。其实看源码开始时只是需要看一个大致的框架和思路,了解代码的大致执行流程,千万不要让自己陷入到细节的泥潭中。所以本文是通过几个关键的接口作为切入点来梳理SpringMVC的执行流程,如果我们把关键的接口弄懂了,也就了解了SpringMVC的执行流程。所以本文只是去了解接口功能,并不关注到具体的实现逻辑上。当我们把大体流程了解后,之后就只是各个击破具体的实现类了。之后作者还会通过自己来实现这些接口来处理自己定义的请求,结合具体的例子来理解。

阅读SpringMVC源码给我最大的感触有两点:

  • 开放封闭原则,SpringMVC中可扩展性很强,我们只需要实现具体的接口,然后将接口加入到容器中,就可以实现我们的扩展功能,不需要改任何代码,对扩展开放。而且已有实现类中的关键方法都是用final修饰的,对修改关闭。
  • 面向接口编程,所有重要的流程代码,几乎都是接口调用,而不是具体到某一个特定的类上面。

源码解读

注:源码版本为 spring-webmvc-5.2.2.RELEASE.jar

几个关键接口和类

HandlerMapping

public interface HandlerMapping {
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; }

HandlerMapping映射了请求与具体处理器的关系,可以理解为内存中有这样一个内存数据 Map<request, Handler>,HandlerMapping就是根据请求从Map中找到Handler返回。HandlerExecutionChain只是将Handler和其对应的拦截器interceptors进行了包装。

前文所说的调度规则,通过请求的 HttpServletRequest 获取具体的请求处理类,将请求处理类包装成 HandlerExecutionChain 返回。其中 HandlerExecutionChain中的Object handler就是具体的请求处理类。

public class HandlerExecutionChain {

	private final Object handler;

	@Nullable
private HandlerInterceptor[] interceptors; @Nullable
private List<HandlerInterceptor> interceptorList;
}

我们现在通过请求找到了具体的处理类,那么我们怎么通过处理类去执行具体的方法呢?那么就需要HandlerAdapter了。

HandlerAdapter

public interface HandlerAdapter {

    /**
* 通过方法 supports 判断适配器是否适配这种类型的Handler,返回true则代表适配。
*/
boolean supports(Object handler); /**
* 如果适配则通过方法 handle 去让 Object handler 执行具体的处理方法。
*/
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response
, Object handler) throws Exception; long getLastModified(HttpServletRequest request, Object handler); }

HandlerAdapter 处理器的适配器,帮助前端控制器去执行处理器Handler中具体的处理业务,让前端控制机不需要关注具体的执行细节,也就是说HandlerAdapter对前端控制机屏蔽了处理器执行的具体细节。

ModelAndView

public class ModelAndView {

	/** View instance or view name String. */
@Nullable
private Object view; /** Model Map. */
@Nullable
private ModelMap model;

对逻辑视图名view和数据模型的封装。

Object view为通常为String类型的逻辑视图名。

ModelMap 为MVC中Model的角色,底层为Map类型。

ViewResolver

public interface ViewResolver {
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception; }

解析逻辑视图名viewName找到具体的View,前端控制器找到具体视图View的向导。

View

public interface View {
void render(@Nullable Map<String, ?> model, HttpServletRequest request
, HttpServletResponse response) throws Exception; }

调用render方法通过数据模型渲染视图。

请求参数中的 Map<String, ?> model 在SpringMVC中扮演Model的角色。

比如我们想返回json格式的数据,那么render方法逻辑就是将model转为json格式输出。

或者我们想返回jsp,那么我们就可以model解析到具体的jsp页面进行展示。

前端控制器 DispatcherServlet

 在SpringMVC中,DispatcherServlet作为前端控制器,控制服务的具体执行流程,主要的执行流程代码也都在这个类中。

下面是DispatcherServlet简化的源码,只包含了重要的执行流程,保留了关键的执行代码,读者可以具体关键字进行搜索去找到具体的代码行。

public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); render(mv, request, response);
} protected void render(ModelAndView mv, HttpServletRequest request
, HttpServletResponse response) throws Exception {
String viewName = mv.getViewName();
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
view.render(mv.getModelInternal(), request, response);
}
代码流程:
  • 获取处理器:HandlerMapping通过HttpServletRequest请求找到具体的Handler
  • 获取处理器对应的适配器:通过Handler找到具体的HandlerAdapter
  • 调用处理器的处理逻辑:HandlerAdapter调用Handler执行具体的处理逻辑,返回ModelAndView
  • 解析视图:ViewResolver通过ModelAndView中的逻辑视图名找到具体的View。
  • 渲染视图:View将数据模型进行渲染。
获取处理器
 @Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

SpringMVC在启动的时候会扫描所有实现了HandlerMapping接口的类,并将这些类加入到容器中。

获取处理器其实就是循环实现了HandlerMapping的类,调用getHandler()方法,找到了就停止并返回。

每种类型的Handler都有各自对应的HandlerMapping。比如SpirngMVC中默认的处理器为 Controller,与之对于的HandlerMapping为RequestMappingHandlerMapping。

获取处理器的适配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
}

逻辑和获取处理器一样,判断逻辑就是前面提过的,只要supports方法返回true,则代表适配这个Handler。

同理也是一种Handler应该有与之对应的HandlerAdapter。与Controller对应的为RequestMappingHandlerAdapter。

所以如果我们要编写自定义的处理器。那么我们需要自己的Handler类和与之对于的HandlerMapping和HandlerAdapter。

解析视图
 @Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}

仍然是同样的逻辑,所有的代码都是面向接口来开发的。

SpringMVC支持各种各样的视图渲染,如JSP、json、freemarker、thymeleaf。ViewResolver就是这些视图的向导,它告诉SpringMVC需要通过什么方式去找到具体的视图View。

每种视图View都有自己对应的视图解析器,例如FreeMarkerView对应的视图解析器为FreeMarkerViewResolver。

视图渲染

其实就一句代码。

view.render(mv.getModelInternal(), request, response);

视图渲染,作者刚开始看到这个词,觉得好高大上。其实就是让数据模型model应以什么样的方式来展示。

如果你想将model以json格式返回,那么你就去实现View接口,把model转为json格式,然后写入到响应类的输出流即可。

ServletOutputStream out = response.getOutputStream();
baos.writeTo(json);
out.flush();

结语

本文只是通过几个重要的接口来描述SpringMVC的执行流程,没有具体分析实现类的逻辑。也想在这里分享下自己看源码的心得体会。看源码时千万不要让自己陷入过深的业务逻辑中去,先看主要执行流程,重要的接口,比如以debug的方式先预览下执行的方法栈,根据方法栈去定位。如果有哪些地方有误或者有不同的理解,还请不吝赐教。

从源码角度了解SpringMVC的执行流程的更多相关文章

  1. angularjs源码分析之:angularjs执行流程

    angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题.其中涉及到很多概念,比如:directive,controller,service,compile,l ...

  2. Netty 源码 NioEventLoop(三)执行流程

    Netty 源码 NioEventLoop(三)执行流程 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 上文提到在启动 N ...

  3. mybatis源码学习:插件定义+执行流程责任链

    目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...

  4. MyBatis源码解析(一)——执行流程

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6603926.html 一.MyBatis简介 MyBatis框架是一种轻量级的ORM框架, ...

  5. 通过自己实现接口来加深理解SpringMVC的执行流程

    功能介绍 上篇文章[从源码角度了解SpringMVC的执行流程]通过接口源码向大家介绍了SpringMVC的执行流程,主要偏重于源码.这篇文件我们来自己实现那几个关键接口,来真实体验下SpringMV ...

  6. Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程

    在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),G ...

  7. 【react】什么是fiber?fiber解决了什么问题?从源码角度深入了解fiber运行机制与diff执行

    壹 ❀ 引 我在[react] 什么是虚拟dom?虚拟dom比操作原生dom要快吗?虚拟dom是如何转变成真实dom并渲染到页面的?一文中,介绍了虚拟dom的概念,以及react中虚拟dom的使用场景 ...

  8. 源码深度解析SpringMvc请求运行机制(转)

    源码深度解析SpringMvc请求运行机制 本文依赖的是springmvc4.0.5.RELEASE,通过源码深度解析了解springMvc的请求运行机制.通过源码我们可以知道从客户端发送一个URL请 ...

  9. Android -- 带你从源码角度领悟Dagger2入门到放弃

    1,以前的博客也写了两篇关于Dagger2,但是感觉自己使用的时候还是云里雾里的,更不谈各位来看博客的同学了,所以今天打算和大家再一次的入坑试试,最后一次了,保证最后一次了. 2,接入项目 在项目的G ...

随机推荐

  1. [转]爬虫 selenium + phantomjs / chrome

    目录 selenium 模块 安装 phantomjs 浏览器 安装 chromedriver 接口 安装 对比两个接口 整合使用 基本实例 常用属性方法 定位节点 节点操作 其他操作 实例解析 - ...

  2. html(四)数据库curd操作与分页查询

    数据库操作curd : 1.首先要建立项目处理好自己逻辑包: 其中util工具包中建立两个工具类 jdbc连接和page分页 DBUtil.java: db工具类就是用于连接数据库的jdbc架包,里面 ...

  3. H3C 最大跳数16导致网络尺度小

  4. hihocoeder1384

    hihocoeder1384 算法竞赛进阶指南上的题目 我们肯定是吧最大值和最小值匹配,次大值和次小值匹配以此类推 首先,类似于区间覆盖的思想,我们对于一个\(L\),找到最大的满足条件的\(R\) ...

  5. 【土旦】vue 解决ios H5底部输入框 获取焦点时弹出虚拟键盘挡住输入框 以及监听键盘收起事件

    问题描述 im聊天H5页面,在iOS系统下,inpu获取焦点弹出系统虚拟键盘时,会出现挡住input的情况,十分影响用户体验. bug图 解决方法: html: <input type=&quo ...

  6. ZOJ Problem Set - 1090——The Circumference of the Circle

      ZOJ Problem Set - 1090 The Circumference of the Circle Time Limit: 2 Seconds      Memory Limit: 65 ...

  7. Ubuntu14.04虚拟机下基本操作(typical安装)

    1.打开终端:ctrl+alt+T 2.图形桌面和命令行界面切换:Ctrl+Alt+F1和Ctrl+Alt+F7 3.切换到root用户:激活前,sudo su+回车: 激活后,su+回车.  切换回 ...

  8. 浅析 Nginx 网络事件

    Nginx 是一个事件驱动的框架,所谓事件主要指的是网络事件,Nginx 每个网络连接会对应两个网络事件,一个读事件一个写事件.在深入了解 Nginx 各种原理及在极端场景下的一些错误场景处理时,需要 ...

  9. 记一次线上 OOM 和性能优化

    大家好,我是鸭血粉丝(大家会亲切的喊我 「阿粉」),是一位喜欢吃鸭血粉丝的程序员,回想起之前线上出现 OOM 的场景,毕竟当时是第一次遇到这么 紧脏 的大事,要好好记录下来. 1 事情回顾 在某次周五 ...

  10. 洛谷$P3647\ [APIO2014]$连珠线 换根$dp$

    正解:换根$dp$ 解题报告: 传送门! 谁能想到$9102$年了$gql$居然还没写过换根$dp$呢,,,$/kel$ 考虑固定了从哪个点开始之后,以这个点作为根,蓝线只可能是直上直下的,形如&qu ...