springmvc 在web 项目中基本上是必用的,基于servlet的一种控制层框架,我们在使用的过程中往往会碰到各种问题,有时候还没有思绪解决问题,主要是因为我们对请求处理的流程不熟悉,不能快速定位都问题所在,所以总会耗费大量时间,各种百度才解决问题。如果你也有这样的困惑,不妨一起来看下springmvc 的请求处理流程。让我们能够快速定位问题。
您将了解到:
其中前面三个为核心组件。
HandlerMapping 是⽤来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是⽅法。⽐如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler。Handler负责具体实际的请求处理,在请求到达后,HandlerMapping 的作⽤便是找到请求相应的处理器Handler 和 Interceptor.
HandlerAdapter 是⼀个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的⽅法结构都是doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理⽅法调⽤ Handler 来进⾏处理,便是 HandlerAdapter 的职责。
ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情:ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个InternalResourceViewResolver,是针对 JSP 类型视图的。
HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况。它的作⽤是根据异常设置 ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯。
RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,便要通过这个组件从请求中查找 ViewName。
ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这个组件也是 i18n 的基础。
MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还可以调⽤ getFileMap()⽅法得到Map
FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完 post 请求之后重定向到⼀个 get 请求,这个 get 请求可以⽤来显示订单详情之类的信息。这样做虽然可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通过 FlashMap 来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过 ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。
这九大组件都在 DispatcherServlet 类中。
/** MultipartResolver used by this servlet. */ // 多部件解析器
@Nullable
private MultipartResolver multipartResolver; /** LocaleResolver used by this servlet. */ //地域解析器,国际化
@Nullable
private LocaleResolver localeResolver;
/** ThemeResolver used by this servlet. */ //主题解析器
@Nullable
private ThemeResolver themeResolver; /** List of HandlerMappings used by this servlet. */
//处理器映射器组件
@Nullable
private List handlerMappings;
/** List of HandlerAdapters used by this servlet. */
//处理器适配器组件
@Nullable
private List handlerAdapters;
/** List of HandlerExceptionResolvers used by this servlet. */
//处理器异常解析器
@Nullable
private List handlerExceptionResolvers;
/** RequestToViewNameTranslator used by this servlet. */
//视图名称转换器
@Nullable
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMapManager used by this servlet. */ //flash 组件管理器
@Nullable
private FlashMapManager flashMapManager;
/** List of ViewResolvers used by this servlet. */
//视图解析器
@Nullable
private List viewResolvers;
三层架构:表现层、业务层和持久层
MVC 设计模式:
MVC 全名是 Model View Controller,是 模型(model)-视图(view)-控制器(controller) 的缩写, 是⼀
种⽤于设计创建 Web 应⽤程序表现层的模式。MVC 中每个部分各司其职:
Model(模型):模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业
务。
View(视图): 通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的。通常视图是依据
模型数据创建的。
Controller(控制器): 是应⽤程序中处理⽤户交互的部分。作⽤⼀般就是处理程序逻辑的。
MVC提倡:每⼀层只编写⾃⼰的东⻄,不编写任何其他的代码;分层是为了解耦,解耦是为了维
护⽅便和分⼯协作。
所有请求都会通过DispatcherServlet 来执行。
流程说明:
1、DispatcherServlet 接收到客户端发送的请求。
2、DispatcherServlet 收到请求调用HandlerMapping 处理器映射器。
3、HandleMapping 根据请求URL 找到对应的handler 以及处理器 拦截器,返回给DispatcherServlet
4、DispatcherServlet 根据handler 调用HanderAdapter 处理器适配器。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
5、HandlerAdapter 根据handler 执行处理器,也就是我们controller层写的业务逻辑,并返回一个ModeAndView
6、HandlerAdapter 返回ModeAndView 给DispatcherServlet
7、DispatcherServlet 调用 ViewResolver 视图解析器来 来解析ModeAndView
8、ViewResolve 解析ModeAndView 并返回真正的view 给DispatcherServlet
9、DispatcherServlet 将得到的视图进行渲染,填充到request域中
10、返回给客户端响应结果。
请求进来。先找HttpServlet.发现FrameworkServlet 重现了doGet()和doPost() 方法。所以会执行其中的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mjZYTff8-1659089399578)(https://upload-images.jianshu.io/upload_images/28037261-b4863e89a9c8c2b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CYBmZpzG-1659089399579)(https://upload-images.jianshu.io/upload_images/28037261-cf716ab34f243c25.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
并且在FrameworkServlet 中实现了HttpServlet 的 doGet()和doPost() 方法。
我们接着看FrameworkServlet 的doGet和doPost 方法都是调用其processRequest 方法。这个方法里面主要的调用DispatcherServlet中doService()方法。
在DispatcherServlet 中的这个方法。会调用doDispatche() 方法执行。
所以整体的请求流程都在doDispatch() 方法中。
主要流程如下:
1、根据url 请求获取到handlerExecutorChina 对象,也就是获取到handler执行链对象,这个对象中包含执行的handler 和一个拦截器的集合。
2、根据这个handlerExecutorChina 执行链对象获取到对应的handlerAdapter handler适配器。
3、根据这个适配器,真正执行handler ,并且返回一个modeAndView 这里的handler 就是我们通常所有的controller 层的业务逻辑。
4、根据获取到的modeAndView ,解析渲染页面并返回结果。
这样整体核心流程就完了。只有这四步。其实和最开始的流程处理图是一样的。总结一下:先根据请求从handlermapping 中获取到handle ,然后通过handle 找到能处理该请求的 handlerAdapter ,通过handlerAdapter 执行真正的handler 也就是我们的业务代码,并返回一个 ModeAndView 。然后通过viewResolver 解析成view 返回给页面
九大组件初始化是在 DispatcherServlet 中的onRefresh() 方法中。
我们通过调用栈可以发现,是在applicationContext类中的refresh() 方法中执行的。这个方法我们很熟悉就是spring 框架的初始化流程执行的主要方法。在refresh() 方法中 完成的Bean 对象实例化流程之后,会执行事件发布。这样就来到了我们springmvc 初始化了。
这九大组件的初始化流程都是差不多的,我们来看下核心组件的HandlerMapping 组件的初始化。
先在ApplicationContext中查找所有HandlerMappings,包括祖先上下文 或者指定的名称还获取,如果获取不到,就会从默认的配置中获取handlermappings
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ib4OhfFz-1659089399591)(https://upload-images.jianshu.io/upload_images/28037261-0f359ba82c8e5978.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
获取默认的handlermappings 主要是从defaultStrategies 中获取。
那defaultStrategies 数据是怎么来的呢?
可以看到这个Properties 是从DispatcherServlet.properties 配置文件来的。
我们查看这个文件可以看到默认的配置信息,九大组件的默认配置信息都有。
下面我们回到主流程,第一步,从handlermappingshandlerMappings 中获取对应的handler
可以看到就是遍历handlerMappings 找到这个url 对应的handler 就完了。
一样的遍历handlerAdpters ,获取到可以处理这个handler 的适配器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FgjmXnaw-1659089399595)(https://upload-images.jianshu.io/upload_images/28037261-0af15bd81e388e72.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kAFG1Krd-1659089399596)(https://upload-images.jianshu.io/upload_images/28037261-f17811f3acef83f0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
可以根据调用栈很好地找到调用流程。主要就是检测request ,然后通过url 找到对对应的方法,填充方法需要的参数值,通过反射调用该方法。
render⽅法完成渲染
视图解析器解析出View视图对象
2、在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同的情况封装的是不同的View实现
3、解析出View视图对象的过程中,要将逻辑视图名解析为物理视图名
4、封装View视图对象之后,调⽤了view对象的render⽅法
5、渲染数据
6、把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后在jsp中可以从请求域取出来的根本原因
7、将数据设置到请求域中
主要了解请求处理的流程,在 DispatcherServlet 中,先通过请求从handlerMappings 中获取对应的handler ,然后通过handler 找到对应的 handlerAdapter 然后通过handlerAdapter 的handle() 方法真正执行我么controller 层的业务逻辑。并返回一个 ModeAndView 然后通过 ViewResolver 解析成view 并渲染到界面上。
当然这中间还有拦截器,主要在获取 handlerAdapter 后,会先执行拦截器方法,然后才执行真正的handler 方法。