本篇博文以MVC原理为基础,讲解了MVC的架构概念 需要解决的问题,以及使用SpringMVC搭建项目示例让读者了解MVC架构的优秀实现者SpringMVC框架,最后以DispatcherServlet简要的分析了SpringMVC的请求和响应流程。希望这篇博文能让大家更好的理解SpringMVC的相关原理。
目录
-
MVC框架
1、何为MVC框架
- M 即MODEL 模型对象 用于web进行请求和响应的数据传输对象
- V 即VIEW 视图对象 用户呈现响应数据的视图展示
- C 即Controller 控制对象 用户对web请求数据进行逻辑处理
MVC架构模式 将数据,视图展示、逻辑处理分离出来,便于我们针对某一方面(比如逻辑处理的变更不会变动视图和处理、或者视图变更不会影响业务逻辑)
2、MVC框架解决的问题
- 当浏览器发送一个http请求,web是如何接受这个请求并指定相应的java类来执行业务逻辑并返回处理结果的?
- web 应用的是典型的“请求--响应”模式的应用,数据是如何顺利流转于浏览器和java世界之间的?面对http协议和java世界数据的不匹配性,我们如何能够做到在流转时数据类型的自动转换?
- Web容器是一个典型的多线程环境,针对每个http请求,web容器的线程池会分配一个特定的线程进行处理。那么如何保证在多线程环境下, 处理请求的java类是线程安全的对象?如何保证数据的流转和访问都是线程安全的?
这个不是web容器的概念吗?跟MVC框架有啥关系
- Controller层作为MVC的核心控制器,如何能够在最大程度上支持功能点上的拓展?
- view层的表现形式是多种多样的,随着web开发技术的不断发展,mvc如何在框架级别提供一种完全透明的方式来应对不同的视图表现形式?
- MVC模式虽然很直观的为我们规定了表示层的各种元素,但是如何通过某种机制把这些元素有机整合在一起,从而成为一个整体呢?
总的来说分成三大部分 1、将web页面的请求传给服务器
- 根据不同的请求处理不同的逻辑单元
- 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做了那些工作。
- Servlet 通过调用 init () 方法进行初始化。
- Servlet 调用 service() 方法来处理客户端的请求。
- 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事件
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
public void onApplicationEvent(ContextRefreshedEvent event) {
//标记刷新事件正在处理 如果该标识为false 则会进行手动调用onRefresh
this.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);
//初始化国际化资源解析器LocaleResolver
initLocaleResolver(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) {
@Override
public 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、ThemeSource
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());
//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()方法主要处理逻辑如下:
- 针对include请求进行快照request属性的备份。
- 为request设置一些属性对象方便其后续的请求处理,设置的请求对象又可以分成两类一类是处理视图解析的四个属性分别是WebApplicationContext,localeResolver、themeResolver、ThemeSource,另一类是用于请求重定向时候的传参INPUT_FLASH_MAP、 OUTPUT_FLASH_MAP 、flashMapManager。
- 具体执行针对不同请求进行处理的核心方法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/" 开头的
// 会将所有的请求都包装成MultipartRequest
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//通过请求获取到对应Handler处理器 和其中匹配的拦截器 组成HandlerExecutionChain
mappedHandler = 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 afterCompletion
if (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()