MVC框架请求处理
为开发团队选择一款优秀的MVC框架是件难事儿,在众多可行的方案中决择需要很高的经验和水平。你的一个决定会影响团队未来的几年。要考虑方面太多:
- 简单易用,以提高开发效率。使小部分的精力在框架上,大部分的精力放在业务上。
- 性能优秀,这是一个最能吸引眼球的话题。
- 尽量使用大众的框架(避免使用小众的、私有的框架),新招聘来的开发人员有一些这方面技术积累,减低人员流动再适应的影响。
如果你还在为这件事件发愁,本文最适合你了。选择Spring MVC吧。本篇文章主要以Spring MVC为例,基本上市面上的MVC框架处理流程都大同小异,主流程都基本相同。Spring MVC比较成熟、使用也比较广泛,设计理念也非常棒,所以,本文重点以Spring MVC讲解为主。
先说下在整个WEB请求处理过程中,本篇文章讲述的是哪个流程模块。为直观明了,先上一张图,红色部分为本章所述模块:
1 Spring MVC核心类与接口#
先来了解一下,几个重要的接口与类。现在不知道他们是干什么的没关系,先混个脸熟,为以后认识他们打个基础。
- DispatcherServlet -- 前置控制器
Spring提供的前置控制器,所有的请求都经过它来统一分发。在DispatcherServlet将请求分发给Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。
- HandlerMapping接口 -- 处理请求的映射
HandlerMapping接口的实现类:
SimpleUrlHandlerMapping 通过配置文件,把一个URL映射到Controller类上;
DefaultAnnotationHandlerMapping 通过注解,把一个URL映射到Controller类上;
- HandlerAdapter接口 -- 处理请求的映射
AnnotationMethodHandlerAdapter类,通过注解,把一个URL映射到Controller类的方法上;
- Controller接口 -- 控制器
由于我们使用了@Controller注解,添加了@Controller注解的类就可以担任控制器(Action)的职责,所以我们并没有用到这个接口。
需要为并发用户处理请求,因此实现Controller接口时,必须保证线程安全并可重用。Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前置控制器,ModelAndView中包含了模型(Model)和视图(View)。从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程中的控制器,而ModelAndView是Http请求过程中返回的模型(Model)和视图(View)。
- HandlerInterceptor接口 -- 拦截器
无图,我们自己实现这个接口,来完成拦截的器的工作。
- ViewResolver接口的实现类
Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。
UrlBasedViewResolver类,通过配置文件,把一个视图名交给到一个View来处理;
InternalResourceViewResolver类,比上面的类,加入了JSTL的支持;
- View接口
JstlView类
- LocalResolver接口
- HandlerExceptionResolver接口 -- 异常处理
SimpleMappingExceptionResolver实现类
- ModelAndView类
2 DispatcherServlet初始化过程#
当Web项目启动时,做初始化工作,所以我们大部分是配置在Web.xml里面,这样项目一启动,就会执行相关的初始化工作,下面是Web.xml代码:
<servlet>
<servlet-name>SpringMVCDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-mvc.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVCDispatcher</servlet-name>
<url-pattern>*.jhtml</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>HessianDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:hessian-service.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HessianDispatcher</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
这里配置了两个DispatcherServlet,后面会介绍到,怎么各自处理,有各自的上下文容器。
最早我们开始学习MVC结构时,就是学servlet,都是继承了HttpServlet 类,也是重新了init、doGet、doPost、destroy方法,我这边就不介绍HttpServlet类,DispatcherServlet也是间接最高继承了HttpServlet,类继承结构如图所示:
我们先了解项目启动,DispatcherServlet和父类都做了什么事情呢?这是我们本节的重点。
2.1 第一步:HttpServletBean类init()方法##
DispatcherServlet继承了FrameworkServlet,FrameworkServlet继承了HttpServletBean,HttpServletBean继承了HttpServlet 类,而HttpServletBean类有一个入口点就是重写了init方法,如图所示:
init方法做了什么事情呢?接下来我们来具体分析:
- PropertyValues:获取Web.xml里面的servlet的init-param(web.xml)
/**
* Create new ServletConfigPropertyValues.
* @param config ServletConfig we'll use to take PropertyValues from
* @param requiredProperties set of property names we need, where
* we can't accept default values
* @throws ServletException if any required properties are missing
*/
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Enumeration en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = (String) en.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
}
}
说明:
Enumeration en = config.getInitParameterNames();
获取了init-param的param-name和param-value值,并设置配置参数到PropertyValue,如图所示:
- BeanWrapper:封装了bean的行为,提供了设置和获取属性值,它有对应的BeanWrapperImpl,如图所示:
- ResourceLoader:接口仅有一个getResource(String location)的方法,可以根据一个资源地址加载文件资源。classpath:这种方式指定SpringMVC框架bean配置文件的来源。
ResourcePatternResolver扩展了ResourceLoader接口,获取资源:
ResourcePatternResolver resolver =new PathMatchingResourcePatternResolver();
resolver.getResources("classpath:spring-mvc.xml");
总结:
- 先通过PropertyValues获取web.xml文件init-param的参数值;
- 然后通过ResourceLoader读取.xml配置信息;
- BeanWrapper对配置的标签进行解析和将系统默认的bean的各种属性设置到对应的bean属性;
2.2 第二步:FrameworkServlet类initServletBean()方法##
在init方法里还调用了initServletBean();
这里面又实现了什么。HttpServletBean在为子类提供模版、让子类根据自己的需求实现不同的ServletBean的初始化工作,这边是由HttpServletBean的子类FrameworkServlet来实现的,如图所示:
this.webApplicationContext = initWebApplicationContext();
初始化SpringMVC 上下文容器,servlet的上下文容器是ServletContext。对initWebApplicationContext();进行跟踪,查看这个方法做了什么事情?
protected WebApplicationContext initWebApplicationContext() {
//1. 根节点上下文,是通过ContextLoaderListener加载的,服务器启动时,最先加载的
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//2. 要对上下文设置父上下文和ID等
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//3. Servlet不是由编程式注册到容器中,查找servletContext中已经注册的WebApplicationContext作为上下文
if (wac == null) {
wac = findWebApplicationContext();
}
//4. 如果都没找到时,就用根上下文就创建一个上下文有ID
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
//5. 在上下文关闭的情况下调用refesh可启动应用上下文,在已经启动的状态下,调用refresh则清除缓存并重新装载配置信息
if (!this.refreshEventReceived) {
onRefresh(wac);
}
//6. 对不同的请求对应的DispatherServlet有不同的WebApplicationContext、并且都存放在ServletContext中
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
总结:
initWebApplicationContext初始化上下文,并作为值放到了ServletContext里,因为不同的DispatherServlet有对应的各自的上下文,而且上下文有设置父上下文和id属性等。上下文项目启动时会调用createWebApplicationContext()方法,如下图所示。
然后会初始化,设置父上下文和id属性等,如图所示:
获取ContextLoaderListener加载的上下文并标示为根上下文,如果是编程式传入,没初始化,以根节点为父上文,并设置ID等信息,然后初始化。
如果上下文是为空的,Servlet不是由编程式注册到容器中,查找servletContext中已经注册的WebApplicationContext作为上下文,如果都没找到时,就用根上下文就创建一个上下文ID,在上下文关闭的情况下调用refesh可启动应用上下文,在已经启动的状态下,调用refresh则清除缓存并重新装载配置信息。
对不同的请求对应的DispatherServlet有不同的WebApplicationContext、并且都存放在ServletContext中。以servlet-name为key保存在severtContext,前面有配置了两个DispatherServlet,都有各自的上下文容器,如下图所示。
2.3 第三步:DispatcherServlet类onRefresh()方法##
回调函数onRefresh还做了一些提供了SpringMVC各种编程元素的初始化工作, onRefresh在为子类提供模版、让子类根据自己的需求实现不同的onRefresh的初始化工作,这边是由FrameworkServlet的子类DispatcherServlet来实现的,如图所示:
我们现在来分析SpringMVC组件进行初始化,并封装到DispatcherServlet中:
// 初始化上传文件解析器
initMultipartResolver(context);
// 初始化本地解析器
initLocaleResolver(context);
// 初始化主题解析器
initThemeResolver(context);
// 初始化映射处理器
initHandlerMappings(context);
// 初始化适配器处理器
initHandlerAdapters(context);
// 初始化异常处理器
initHandlerExceptionResolvers(context);
// 初始化请求到视图名翻译器
initRequestToViewNameTranslator(context);
// 初始化视图解析器
initViewResolvers(context);
- initHandlerMappings初始化映射处理器:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
说明:
1)detectAllHandlerMappings默认是true,根据类型匹配机制查找上下文及父容器上下文中所有类型为HandlerMapping的bean,将它们作为该类型组件,并放到ArrayList<HandlerMapping>中。
2)detectAllHandlerMappings如果是false时,查找key为handlerMapping的HandlerMapping类型的bean为该类组件,而且 Collections.singletonList只有一个元素的集合。
3)List<HandlerMapping> 是为空的话,使用BeanNameUrlHandleMapping实现类创建该类的组件。
initHandlerMapping会初始化了handlerMethods请求方法的映射,HandlerMapping是处理请求的映射的如图所示:
- initHandlerAdapters适配器处理器:
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
OrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
}
}
}
initHandlerAdapters适配器处理器初始化原理跟initHandlerMappings初始化映射处理器一样。
当DispatcherServlet初始化后,就会自动扫描上下文的bean,根据名称或者类型匹配的机制查找自定义的组件,找不到则使用DispatcherServlet。Properties定义了默认的组件。
HttpServletBean、FrameworkServlet、DispatcherServlet三个不同的类层次,SpringMVC对三个以抽象和继承来实现不用的功能,分工合作,实现了解耦的设计原则。
我们在回顾一下,各自做了什么事情:
- HttpServletBean 主要做一些初始化的工作,将web.xml中配置的参数设置到Servlet中。比如servlet标签的子标签init-param标签中配置的参数。
- FrameworkServlet 将Servlet与Spring容器上下文关联。其实也就是初始化FrameworkServlet的属性webApplicationContext,这个属性代表SpringMVC上下文,它有个父类上下文,既web.xml中配置的ContextLoaderListener监听器初始化的容器上下文。
- DispatcherServlet 初始化各个功能的实现类。比如异常处理、视图处理、请求映射处理等。
3 DispatcherServlet处理请求过程#
Spring MVC请求处理流程描述:
- 用户向服务器发送请求,请求被Spring 前置控制Servelt DispatcherServlet捕获;
- DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
- DispatcherServlet 根据请求获得Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
- 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
HttpMessageConveter:将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等;
数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等;
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中;- Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
- 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet;
- ViewResolver结合Model和View,来渲染视图;
- 将渲染结果返回给客户端;
HttpServlet提供了service方法用于处理请求,service使用了模板设计模式,在内部对于http get方法会调用doGet方法,http post方法调用doPost方法:
进入processRequest方法看下:
其中注册的监听器类型为ApplicationListener接口类型。继续看DispatcherServlet覆写的doService方法:
最终就是doDispatch方法。doDispatch方法功能简单描述一下:
- 首先根据请求的路径找到HandlerMethod(带有Method反射属性,也就是对应Controller中的方法);
- 然后匹配路径对应的拦截器,有了HandlerMethod和拦截器构造个HandlerExecutionChain对象。HandlerExecutionChain对象的获取是通过HandlerMapping接口提供的方法中得到;
- 有了HandlerExecutionChain之后,通过HandlerAdapter对象进行处理得到ModelAndView对象;
- HandlerMethod内部handle的时候,使用各种HandlerMethodArgumentResolver实现类处理HandlerMethod的参数,使用各种HandlerMethodReturnValueHandler实现类处理返回值;
- 最终返回值被处理成ModelAndView对象,这期间发生的异常会被HandlerExceptionResolver接口实现类进行处理;
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);
// 根据请求,获取HandlerExecutionChain对象
mappedHandler = 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.
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;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 实际执行行handle,返回ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
作者:猿码道
链接:https://www.jianshu.com/p/6462e69ce241
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
MVC框架请求处理的更多相关文章
- 自己动手写一个简单的MVC框架(第二版)
一.ASP.NET MVC核心机制回顾 在ASP.NET MVC中,最核心的当属“路由系统”,而路由系统的核心则源于一个强大的System.Web.Routing.dll组件. 在这个System.W ...
- 探索ASP.NET MVC框架之路由系统
引言 对于ASP.NET MVC的路由系统相信大家肯定不陌生.今天我们就深入ASP.NET的框架内部来看一下路由系统到底是怎么通过我们给出的地址(例如:/Home/Index)解析出Controlle ...
- 写自己的ASP.NET MVC框架(上)
http://www.cnblogs.com/fish-li/archive/2012/02/12/2348395.html 阅读目录 开始 ASP.NET程序的几种开发方式 介绍我的MVC框架 我的 ...
- 【WEB】初探Spring MVC框架
Spring MVC框架算是当下比较流行的Java开源框架.但实话实说,做了几年WEB项目,完全没有SpringMVC实战经验,乃至在某些交流场合下被同行严重鄙视“奥特曼”了.“心塞”的同时,只好默默 ...
- Spring MVC框架
这个Spring Web MVC 框架提供了模型视图控制器的架构,这种结构能够被用来开发灵活的和松耦合的Web应用程序. 这种MVC模式能够将应用程序分离成不同的层面,(输入逻辑,业务逻辑,UI逻辑) ...
- [Java] 使用 Spring 2 Portlet MVC 框架构建 Portlet 应用
转自:http://www.ibm.com/developerworks/cn/java/j-lo-spring2-portal/ Spring 除了支持传统的基于 Servlet 的 Web 开发之 ...
- 设计 REST 风格的 MVC 框架
http://www.ibm.com/developerworks/cn/java/j-lo-restmvc/ 传统的 JavaEE MVC 框架如 Struts 等都是基于 Action 设计的后缀 ...
- Portlet MVC框架
Portlet MVC框架 16.1. 介绍 Spring不仅支持传统(基于Servlet)的Web开发,也支持JSR-168 Portlet开发. Portlet MVC框架尽可能多地采用Web ...
- Spring 4 官方文档学习 Web MVC 框架
1.介绍Spring Web MVC 框架 Spring Web MVC 框架是围绕DispatcherServlet设计的,所谓DispatcherServlet就是将请求分发到handler,需要 ...
随机推荐
- ASCII码表(0-127 ) C中的转义字符
所有的ASCII码都可以用“\”加数字(一般是8进制数字)来表示.而C中定义了一些字母前加"\"来表示常见的那些不能显示的ASCII字符,如\0,\t,\n等,就称为转义字符,因为 ...
- ajax ajax基本介绍
jquery中ajax方法参数详解 url 要求是string类型参数(默认为当前页面地址) 发送请求的地址 type 要求是string类型的参数,请求方式(post或get)默认为get.注意其他 ...
- Filesystem has been set read-only problem.
case: when system is running well ,sudden tf became read-only; analyze: read the kernel log using dm ...
- SQL Server 自动化运维系列 - 监控磁盘剩余空间及SQL Server错误日志(Power Shell)
需求描述 在我们的生产环境中,大部分情况下需要有自己的运维体制,包括自己健康状态的检测等.如果发生异常,需要提前预警的,通知形式一般为发邮件告知. 在所有的自检流程中最基础的一个就是磁盘剩余空间检测. ...
- iOS使用UIWebView遇到Error Domain=WebKitErrorDomain Code=101 “The operation couldn’t be completed. (WebKitErrorDomain error 101
现在在接触iOS开发,今天在调试一个界面加载web页面的问题,发现死活无法加载,浏览器里能正常打开,加上相应代码之后得到了错误信息为: 2013-04-18 15:05:06.446 Client_D ...
- OK335xS canutils deal with compile error
/************************************************************************************** * OK335xS ca ...
- linux-*.filetype.bz2 unzip
how to unzip *.bz2 file? wget http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 unzip ...
- No.01——配置编程环境
======由于很喜欢Android编程,所以买来了安卓权威指南来学习.为了应用费曼技巧——把知识输出出去以检验和巩固,在此写下学习笔记======= 1. 配置编程环境 Java的JDK(Java ...
- UIWebView UITextView
// // ViewController.m // 网页 // #import "ViewController.h" @interface ViewController ()< ...
- CodeForces - 457C:Elections(三分)
You are running for a governor in a small city in Russia. You ran some polls and did some research, ...