本篇博文以MVC原理为基础,讲解了MVC的架构概念 需要解决的问题,以及使用SpringMVC搭建项目示例让读者了解MVC架构的优秀实现者SpringMVC框架,最后以DispatcherServlet简要的分析了SpringMVC的请求和响应流程。希望这篇博文能让大家更好的理解SpringMVC的相关原理。

目录

MVC框架

1、何为MVC框架

2、MVC框架解决的问题

3、MVC框架

SpringMVC框架

1、springMVC简介

2、SpringMVC核心组件

3、SpringMVC流程示例

DispatcherServlet入口

init方法

1、ContextRefreshListener刷新事件

2、初始化SpringMVC组件

​service方法

FrameworkServlet

doService()方法

doDispatcher()方法


  • MVC框架

1、何为MVC框架

  1. M  即MODEL 模型对象  用于web进行请求和响应的数据传输对象
  2. V   即VIEW  视图对象  用户呈现响应数据的视图展示
  3. C  即Controller 控制对象 用户对web请求数据进行逻辑处理

MVC架构模式 将数据,视图展示、逻辑处理分离出来,便于我们针对某一方面(比如逻辑处理的变更不会变动视图和处理、或者视图变更不会影响业务逻辑)

2、MVC框架解决的问题

  1. 当浏览器发送一个http请求,web是如何接受这个请求并指定相应的java类来执行业务逻辑并返回处理结果的?
  2. web 应用的是典型的“请求--响应”模式的应用,数据是如何顺利流转于浏览器和java世界之间的?面对http协议和java世界数据的不匹配性,我们如何能够做到在流转时数据类型的自动转换?
  3. Web容器是一个典型的多线程环境,针对每个http请求,web容器的线程池会分配一个特定的线程进行处理。那么如何保证在多线程环境下, 处理请求的java类是线程安全的对象?如何保证数据的流转和访问都是线程安全的?

  这个不是web容器的概念吗?跟MVC框架有啥关系

  1. Controller层作为MVC的核心控制器,如何能够在最大程度上支持功能点上的拓展?
  2. view层的表现形式是多种多样的,随着web开发技术的不断发展,mvc如何在框架级别提供一种完全透明的方式来应对不同的视图表现形式?
  3. MVC模式虽然很直观的为我们规定了表示层的各种元素,但是如何通过某种机制把这些元素有机整合在一起,从而成为一个整体呢?

总的来说分成三大部分 1、将web页面的请求传给服务器

  1. 根据不同的请求处理不同的逻辑单元
  2. 3、返回处理结果并跳转至响应页面。

3、MVC框架

常使用的SpringMVC框架 Jsp+servlet+javaBean、Struct2、SpringMVC、grails

SpringMVC框架

1、springMVC简介

SpringMVC是以请求驱动,基于Servlet功能实现的将web请求转发给控制器,控制器进行相关逻辑处理,转换为数据对象并通过视图解析器将对应的数据展示到特定视图。核心入口是DispatcherServlet类。

2、SpringMVC核心组件

  • DispatcherServlet类
  • 处理器映射器 HandlerMapping
  • 处理器适配器 HandlerAdapte
  • 视图解析器 ResourceViewResolver

3、SpringMVC流程示例

https://github.com/liushangzaibeijing/ssm.git

DispatcherServlet入口

下面我们从Spring核心入口类入手来探究SpringMVC的原理奥妙。

在UML类图中,红色标明的是Servlet规范对应的接口和其实现,其他的为SpringMVC自己的扩展实例,我们重点关注是

HttpServletBean、FrameWorkServlet、DispatcherServlet实现类。

DispatcherServlet类DispatcherServlet本质上是一个Servlet,所以也遵循Servlet的生命周期。我们就从其生命周期入手,看看SpringMVC做了那些工作。

  1. Servlet 通过调用 init () 方法进行初始化。
  2. Servlet 调用 service() 方法来处理客户端的请求。
  3. Servlet 通过调用 destroy() 方法终止(结束)。

init方法

根据DispatcherServlet的继承关系,我们可以梳理出其父类HttpServletBean 重写了init()方法,给其子类留下了一个可扩展的方法initServletBean() 该方法被其父类FrameworkServlet类重写,下面我们都类分析一下HttpServletBean的init()方法和FrameworkServlet的initServletBean()方法。

HttpServletBean的init方法、FrameworkServlet的initServletBean()方法的功能由于篇幅的原因笔者在自己的上篇博文已经介绍过了,请大家参考笔者的该篇博文:servlet体系功能。

initServletBean方法中的initWebApplicationContext()方法的configureAndRefreshWebApplicationContext()方法中注册了一个ContextRefreshListener监听器类,看名字可以知道监听器主要针对容器刷新ContextRefreshedEvent事件进行监听,同时在该方法中的refresh()方法中的finishRefresh()方法中发布了ContextRefreshedEvent事件。

protected void configureAndRefreshWebApplicationContext(
ConfigurableWebApplicationContext wac) {/*** 省略相关代码 **///为容器注册ContextRefreshListener事件wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));/*** 省略相关代码 **///调用的该方法中的finishRefresh()发布了ContextRefreshedEvent 事件wac.refresh();
}

1、ContextRefreshListener刷新事件

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {//处理ContextRefreshEvent事件@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {FrameworkServlet.this.onApplicationEvent(event);}
}public void onApplicationEvent(ContextRefreshedEvent event) {
//标记刷新事件正在处理 如果该标识为false 则会进行手动调用onRefreshthis.refreshEventReceived = true;onRefresh(event.getApplicationContext());
}//DispatcherServlet的onRefresh()方法
protected void onRefresh(ApplicationContext context) {initStrategies(context);
}

追踪源码到这里始终没有接触到DispatchServlet中实现的方法(该实例初始化大部分操作都是调用其父类HttpServletBean/FrameworkServlet)只有onRefresh()方法才是DispatcherServlet()方法,下面我们来关注一下重写的onRefresh()的方法。

2、初始化SpringMVC组件

initStrategies()主要是针对springMVC的9大核心组件的实例化,这九大组件是springMVC功能的基石。

/*** 初始化springMVC的核心九大组件* @param context*/
protected void initStrategies(ApplicationContext context) {//初始化文件上传解析对象组件MultipartResolver//该组件主要用于支持springMVC的文件上传initMultipartResolver(context);//初始化国际化资源解析器LocaleResolverinitLocaleResolver(context);//初始化主题资源解析器 一个主题就是一组静态资源  其包含主题资源和主题解析器initThemeResolver(context);//初始化处理器映射器 该组件主要是根据对应的请求 获取对应的处理逻辑组件Handler//(说白了就是我们编写的Controller的方法)initHandlerMappings(context);//初始化处理器适配器,因为我们编写的Handler 可能是不同的了类型,比如简单类型,http类型等//为了使请求可能被不同的handler处理统一起来 这里会使用适配模式提供统一的处理请求的返回结果的接口//所有业务逻辑执行是在该处理器适配器中执行的//比如:我们国家的电压220V 但是和手机,洗衣机需要的电压,这里充电的时候会有不同的适配器将其电压转换为//合适终端的电压,此即为处理器适配器initHandlerAdapters(context);//初始化 处理异常的组件,该组件有一个方法resolveException() 该方法会对请求处理过程中出现异常的情况//进行处理 根据不同的异常返回不同的异常页面initHandlerExceptionResolvers(context);//当Controller处理方法并没有返回视图的时候,且没有在reponse存放数据(往reponse中存放数据大多数是下载功能)//该组件按照其getViewName()设置视图 从而返回initRequestToViewNameTranslator(context);//初始化视图解析器,当请求被处理放入ModelAndView.该组件会选择合适的视图去进行渲染initViewResolvers(context);//初始化FlashMapManager 用于在重定向的时候 还能继续使用数据(一般情况重定向请求后请求参数会丢失)initFlashMapManager(context);
}
 
组件类 相关描述
MultipartResolver

文件处理器,用于支持springMVC文件上传,将普通的request包装成MultipartHttpServletRequest

LocaleResolver 国际化资源组件, 用过其resolveLocale()方法来获取对应的国际化资源
ThemeResolver 主题资源解析组件,springMVC通过该组件展示不同的主题
HandlerMappings 处理器映射器,根据请求获取对应的处理器Handler(Controller)
HandlerAdapters 处理器适配器,通过适配不同的处理器,对请求信息提供统一的处理方式
HandlerExceptionResolvers
 
Handler处理器处理逻辑发生异常,该组件会针对不同的异常进行相关的处理 返回不同的异常视图信息
RequestToViewNameTranslator 对于请求响应无视图的情况下,提供默认的视图名称进行处理
ViewResolvers 视图解析器,针对ModelAndView 建立不同的视图并进行渲染
FlashMapManager 始化FlashMapManager 用于在重定向的时候 还能继续使用数据

service方法

FrameworkServlet

HttpServletBean主要参与了DispatchServlet的初始化操作,其请求处理并没有相关的重写,主要的处理请求逻辑的功能是放在了FrameworkServlet和DispatchServlet。

FrameworkServlet重写了HttpServlet的service(),doGet,doPost,doPut,doDelete,doHead,doOptions,doTrace方法。请求方式

在service()方法增加了对patch请求的支持,对于其他请求类型交由其父类实现,父类HttpServlet 的service() 针对不同的请求方式实现了doXXx()方法 子类FrameworkServlet又重写了该方法,所以不同的请求方式最终还是调用FrameworkServlet重写了的doXXX()方法。

PATCH请求方式

方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新,

PUT方式是对资源某个字段更新的时候需要将资源的所有信息都携带过去,

但是使用PATCH方法只需要携带资源的部分信息即可。

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {//针对PATCH请求进行单独处理 走processRequest方法  String method = request.getMethod();if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {processRequest(request, response);}else {//其他请求方法走HttpServlet的service()方法super.service(request, response);}
}//在上述的七个方法中 除了doOptions doTrace方法需要判断是否走自己的processRequest
//还是调用父类的方法
protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {if (this.dispatchTraceRequest) {processRequest(request, response);if ("message/http".equals(response.getContentType())) {// Proper TRACE response coming from a handler - we're done.return;}}super.doTrace(request, response);
}
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {if (this.dispatchOptionsRequest) {processRequest(request, response);if (response.containsHeader("Allow")) {// Proper OPTIONS response coming from a handler - we're done.return;}}super.doOptions(request, new HttpServletResponseWrapper(response) {@Overridepublic void setHeader(String name, String value) {if ("Allow".equals(name)) {value = (StringUtils.hasLength(value) ? value + ", " : "") + RequestMethod.PATCH.name();}super.setHeader(name, value);}});
}

上面所有方法最终都调用processRequest方法,有关该方法的解读请参考:processRequest方法解读。

doService()方法

在processRequest方法中我们发现了一个核心方法doService(),该方法是由DispatcherServlet实现的,用来处理web请求的核心方法,也是SpringMVC功能实现的核心。


protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {//在include请求完成之后,恢复之前调用include请求的数据信息//比如 '/aa' 请求include '/bb' 则在处理'/bb'请求时候先针对‘/aa’请求的一些数据进行快照保存//方便在‘/bb’请求结束后恢复‘/aa’的请求信息//对应请求是include请求的,需要先将inlude请求之前的数据进行快照备份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));}}}//为request设置额外的属性信息//1、视图解析 四个属性WebApplicationContext、localeResolver、themeResolver、ThemeSourcerequest.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());//2、设置重定向传参相关// 2.1、INPUT_FLASH_MAP 用于保存上一个请求重定向过来的参数// 2.2、OUTPUT_FLASH_MAP 用于保存重定向到别的请求需要传递的参数// 2.3、flashMapManager 用于支持重定向传参的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()方法主要处理逻辑如下:

  1. 针对include请求进行快照request属性的备份。
  2. 为request设置一些属性对象方便其后续的请求处理,设置的请求对象又可以分成两类一类是处理视图解析的四个属性分别是WebApplicationContext,localeResolver、themeResolver、ThemeSource,另一类是用于请求重定向时候的传参INPUT_FLASH_MAP、  OUTPUT_FLASH_MAP 、flashMapManager。
  3. 具体执行针对不同请求进行处理的核心方法doDispatcher()方法。

doDispatcher()方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;//处理器链 包含一个Handler和多个与之匹配的拦截器HandlerExecutionChain mappedHandler = null;//用于表示是否文件上传的标识boolean multipartRequestParsed = false;//异步管理器WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {//判断请求是否文件上传请求 如果是文件上传 该方法中使用我们init()方法初始化的//multipartResolver文件上传解析组件//如果有文件解析组件且通过isMultipart()方法判断contentType的"multipart/" 开头的// 会将所有的请求都包装成MultipartRequestprocessedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);//通过请求获取到对应Handler处理器 和其中匹配的拦截器 组成HandlerExecutionChainmappedHandler = getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {noHandlerFound(processedRequest, response);return;}//根据处理器获取对应的拦截器适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 对于请求是get或者head请求 有LastModified的判断//第一次请求的时候返回的响应信息会包含该资源的lastModified最后修改时间//下次在进行请求会将lastModified带过来与资源的lastModified比较 如果时间一致//则表明该资源没有被修改 则直接响应给浏览器 浏览器使用其缓存String method = request.getMethod();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;}}//执行HandlerExecutionChain 中所有拦截器的preHandle()方法if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 最终使用适配器处理Handler 返回ModelAndView对象mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//如果异步处理则此处直接返回if (asyncManager.isConcurrentHandlingStarted()) {return;}//如果ModelAndView没有view对象则使用我们在之前初始化的RequestToViewNameTranslator组件//为其设置按照一定规则为其设置默认的视图applyDefaultViewName(request, mv);//执行HandlerExecutionChain 中所有拦截器的PostHandle()方法mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}//请求结果的处理 包括页面渲染,异常处理、以及请求完成后触发HandlerExecutionChain中所有拦截器的// triggerAfterCompletion方法 进行资源清理工作processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {//碰到整个处理过程中的外层异常则触发触发HandlerExecutionChain中所有拦截器的//triggerAfterCompletion方法 进行资源清理工作triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Error err) {///碰到整个处理过程中的外层error异常则触发触发HandlerExecutionChain中所有拦截器的// 并返回ServletException异常triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);}finally {//异步处理的AsyncHandlerInterceptor的afterConcurrentHandlingStarted方法执行if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {//清除文件上传的请求对象的包装 如果是文件上传请求的话if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
}

该方法的核心主要有5大步骤

1、处理请求之前请求对象的包装,如果是文件上传请求需要包装成文件上传请求对象,执行结束后文件上传对象包装类对象的清除。涉及到了get head请求的缓存处理

2、根据请求通过HandlerMapping处理器映射器获取对应HandlerExecutionChain 其包含一个Handler处理器和与该处理器匹配的多个拦截器,Handler直接对应我们Controller可以是一个类,也可以是一个方法或者使用@requestMapping修饰的方法。这里我们还需要了解HandlerMapping处理器,该组件其实是用来按照某种规则来查找请求匹配的Handler的

3、通过Handler获取能执行该处理器的适配器,HandlerAdapter 其实是针对我们不同的Handler,提供一个统一的处理方法而衍生出来的使用适配器模式的一个组件用来以一个固定的方法调用灵活可变的Handler

4、执行handler的适配器有了,这里可以使用适配器来进行相关的调用了并返回对应的ModelAndView,调用handler之前之后以及视图渲染之前还会相关的调用其Handler对应的所有拦截器 调用其preHandle、postHandle、afterCompletion方法 三者调用顺序一致,前者正序调用,后两者逆序调用。

5、视图对象返回的结果处理,包括页面渲染,异常处理、以及请求完成后触发HandlerExecutionChain中所有拦截器的triggerAfterCompletion方法 进行资源清理工作。

ategies()

SpringMVC原理分析之一MVC框架相关推荐

  1. SpringMVC背景介绍及常见MVC框架比较

    一.Spring MVC 背景介绍 Spring框架提供了构建Web应用程序的全功能MVC模块.使用Spring可插入的MVC架构,可以选择是使用内置的Spring Web框架还是Struts这样的W ...

  2. Web端服务器推送技术原理分析及dwr框架简单的使用

    转载:http://blog.csdn.net/shimiso/article/details/8151362 1 背景 "服务器推送技术"(ServerPushing)是最近We ...

  3. 服务器推送技术原理分析及dwr框架简单的使用

    1        背景 "服务器推送技术"( ServerPushing)是最近 Web技术中最热门的一个流行术语.它是继" Ajax "之后又一个倍受追捧的  ...

  4. 通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[中篇]:请求响应

    <200行代码,7个对象--让你了解ASP.NET Core框架的本质>让很多读者对ASP.NET Core管道有了真实的了解.在过去很长一段时间中,有很多人私信给我:能否按照相同的方式分 ...

  5. 通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇]

    <200行代码,7个对象--让你了解ASP.NET Core框架的本质>让很多读者对ASP.NET Core管道有了真实的了解.在过去很长一段时间中,有很多人私信给我:能否按照相同的方式分 ...

  6. SpringBoot嵌入Tomcat原理分析

    SpringBoot嵌入Tomcat原理 内嵌Tomcat启动原理 首先,来到启动SpringBoot项目的地方,也就是朱配置类. @SpringBootApplication public clas ...

  7. springmvc流程_基于Spring MVC框架的Http流程分析

    一.问题提出 我们可以方便的利用Spring MVC进行业务开发,请求的大部分工作都被框架和容器封装,使得我们只需要做很少量的工作.但是整个http请求流程是怎么样的?Spring MVC框架在其中起 ...

  8. ssm框架搭建流程及原理分析

    这几天自己想搭建个ssm框架玩一下,有些东西长时间不玩都给忘了,所以自己把整个流程整理了一下,只要跟着步骤,就能顺利完成ssm框架的搭建. 一.搭建步骤: 1.整理jar包      2.对于一个we ...

  9. SpringMVC 搭建maven的web项目、执行过程及原理分析

    该框架为学习刘先森课程所得 idea搭建maven的web项目 工程目录结构 创建一个maven工程并导入依赖 <dependencies><dependency><!- ...

最新文章

  1. connection timed out是什么意思_Java 中的内存溢出和内存泄露是什么?我给你举个有味道的例子...
  2. 英语四级计算机准考证查询,四级成绩查询_四级查分:什么?准考证不见了?!!!_沪江英语...
  3. Python 中reload一个文件时报错 ( reload() argument must be module)
  4. uva 10622——Perfect P-th Powers
  5. Java 将文件的内容复制到另一个文件
  6. React组件的State
  7. html5表单注册应用
  8. 计算机老师的专业发展怎么写,高职计算机教师专业发展研究
  9. kafka下载注意事项
  10. 重磅推荐 | 我精选的15个电子书下载网站!
  11. Eureka-使用教程
  12. fetch_array()与fetch_assoc()的用法
  13. 基于opencv-python的车道线检测(高级)
  14. 来看看怎么通过a标签打开一个对话框
  15. 第三章数程序设计初步--分支结构项目3利息计算器
  16. Springboot快速开发-书本信息管理系统(项目源码)
  17. 前端小白浅谈seo优化以及web性能优化方案
  18. 曹雪芹诗歌中的鸿蒙,曹雪芹的诗词丢失,红楼梦里的诗句
  19. Python Scrapy中文教程,Scrapy框架快速入门!
  20. 阿里云安骑士性能特点与使用场景!

热门文章

  1. 如何处理EDIUS打不开jpg格式的图片的问题
  2. 配置Memcached禁止公网访问
  3. Android应用角标适配方法
  4. arduino 嗡鸣器 音乐_Arduino 控制蜂鸣器播放《小星星》歌曲
  5. 2022.03.10
  6. 2022杭电多校 G - Shinobu loves trip
  7. Android 热修复使用Gradle Plugin1.5改造Nuwa插件
  8. 父项目下复制子项目,yml配置文件变粉红解决办法
  9. Linux 实操篇(CentOS7)
  10. 基于JavaSwing开发房产管理系统(access数据库) 课程设计 大作业