SpringMvc请求过程处理分析
Spring对Servlet规范的实现
Spring中对Servlet规范的实现类是FrameworkServlet类。其中FrameworkServlet继承了HttpServletBean,因此这个类对doPost()、doDelete()等方法都有实现,但是实现都是差不多的,这里就分析常用的post和get请求的处理逻辑。
//HttpServletBean继承了HttpServlet类
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
//省略...
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//进行请求的处理
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//进行请求的处理
processRequest(request, response);
}
//省略...
}
实际的处理逻辑都在processRequest()这个方法,它是一个protected方法,源码如下:
//FrameworkServlet.java
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取当前系统时间,用来计算这个步骤的处理耗时
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取当前服务运行所在地区,在RequestContextFilter中进行处理设值
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//创建相关地区的对象的LocalContext,
LocaleContext localeContext = buildLocaleContext(request);
//获取请求的属性,请求相关属性会在RequestContextHolder中,它是用ThreadLocal实现的
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//创建requestAttributes,ServletRequestAttributes是RequestAttributes子类
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
//从request获取WebAsyncManager,在filter阶段会创建WebAsyncManager,表示是不是异步相应的请求
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//将FrameworkServlet的内部类RequestBindingInterceptor设置到asyncManager中,用于在异步中初始化跟重新设置FrameworkServlet
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//将localeContext跟requestAttributes设置到LocaleContextHolder跟RequestContextHolder中
initContextHolders(request, localeContext, requestAttributes);
try {
//进行业务处理
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
//移除ThreadLocal中的请求相关信息,attribute跟context信息
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
//打印结果
logResult(request, response, failureCause, asyncManager);
//发布请求处理完成事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
在这个方法中主要的逻辑还是准备处理请求需要的上下文和参数等。其中,最重要的就是负责业务处理的doService()方法,它是一个抽象方法,必须由子类来实现。而FrameworkServlet类只有一个子类,那就是大名鼎鼎的DispatchServlet类,来看DispatchServlet类中对doService()方法的实现。
//DispatchServlet.java
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
//如果是一个include请求,<jsp:incluede page="xxx.jsp"/> 这种
//可能在一个请求中嵌套了另外的一个请求,因此需要备份当前请求
Map<String, Object> attributesSnapshot = null;
//是否是一个include请求,通过request中的javax.servlet.include.request_uri属性判断
//JSP在运行期间是会被编译成相应的Servlet类来运行的,
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
//获取包含的请求中的获取A请求的内部B请求设定的spring的策略
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
//设置web应用上下文到请求中,
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
//设置本地解析器
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
//设置主题解析器
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
//设置主题,如果没有设置则为null,默认的为WebApplicationContext
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
//将request跟response保存到一个FlashMap中,FlashMap用来将一个请求跟
//另外一个请求关联起来,通常在redirect的时候有用
if (this.flashMapManager != null) {
//如果当前请求的FlashMap在之前的请求中保存过了,则取出来,并去除对应的缓存
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
//保存FlashMap到属性中
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);
}
}
}
}
以上最重要的就是负责请求分发的doDispatch()方法,看懂doDispatch()方法,就能彻底理解一次HTTP请求进入到Spring之后,要做哪些处理。
//DispatchServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//从request中获取WebAsyncManager
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//检查是不是multipart请求并将请求转化为MultipartHttpServletRequest类型的
processedRequest = checkMultipart(request);
//如果请求不是原来的request请求,则表示是multipart请求并且解析过的
multipartRequestParsed = (processedRequest != request);
//根据request中的url从hanlderMapping中获取
//封装了HandlerInterceptor的HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
//如果不存在对应的处理链则返回
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//从HandlerExecutionChain中获取Handler然后寻找合适的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//获取请求方式
String method = request.getMethod();
boolean isGet = "GET".equals(method);
//检查是不是GET请求
if (isGet || "HEAD".equals(method)) {
//检查当前的get类型的请求的最后修改时间是不是存在的
//这个参数用来减少数据传输用
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
//如果浏览器请求中的最后请求时间跟服务器的最后修改时间一致
//并且是get类型请求则直接返回。
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//调用applyPreHandle方法,执行全部的前置拦截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//执行对应的HandlerAdapter的Handler方法,拿到对应的视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//检查当前的请求是否正在异步处理,如果是的则直接放弃并返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//如果处理的结果返回的视图是空的则使用默认的视图,不为空则用处理的结果
applyDefaultViewName(processedRequest, mv);
//调用applyPostHandle方法执行后置拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
//进行错误视图的处理
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//对正常视图或者错误视图的处理
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
//检查当前的请求是否正在异步处理
if (asyncManager.isConcurrentHandlingStarted()) {
//如果mappedHandler不是null,则调用对应的mappedHandler中的AsyncHandlerInterceptor
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
//对流类型的请求,做后置的处理
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
在网上有很多围绕DispatcherServlet的doDispatch()方法说明Spring如何处理请求的图。借用网上一张图描述了DispatchServlet对请求的处理过程。 
整个流程梳理如下:
- 首先会检查当前的请求是不是multipart/form-data类型的请求,是则将请求转换为MultipartHttpServletRequest类型的request
- 解析出URL从HandlerMapping对象中找到对应的封装了HandlerInterceptor集合的HandlerExecutionChain对象,没有找到就直接返回404错误页面,HandlerExecutionChain里封装了handler和HandlerInterceptor链。
- 从HandlerExecutionChain中获取Handler然后寻找合适的HandlerAdapter,根据HandlerAdapter的supports方法查找。
- 检查是不是get请求,如果是,然后在检查lastModified这个属性选择是直接返回还是进行后续的请求处理。
- 调用前面选择的HandlerAdapter的applyPreHandle方法,取到所有拦截器后,for循环调用每个拦截器HandlerInterceptor的preHandle()方法
- 调用前面选择的HandlerAdapter的handle方法,进行逻辑的处理,反射调用Controller里的方法,返回ModelAndView对象
- 检查当前的请求是否正在异步处理,如果是则直接放弃并返回
- 检查返回的ModelAndView的视图是不是null,是则返回默认的,不是则不处理
- 调用前面选择的HandlerAdapter的applyPostHandle方法,取到所有拦截器后,for循环调用每个拦截器HandlerInterceptor的postHandle()方法
- 对正常返回的或者发生异常时生成的视图进行处理,
- 对异步请求或multipart/form-data类型请求进行后续的处理
虽然整个的request请求的逻辑只有这么多,但是里面的每个步骤都是比较复杂的。
我们在业务代码中经常写的响应前端请求的Controller在哪儿呢,上图中并没有画出。实际上,在应用启动的时候,Spring容器会加载这些Controller类,并且解析出URL对应的处理函数,封装成Handler对象,存储到HandlerMapping对象中。当有请求到来的时候,DispatcherServlet从HanderMapping中,查找请求URL对应的Handler,然后调用执行Handler对应的函数代码,最后将执行结果返回给客户端。
为什么要把Controller封装成Handler对象呢,因为定义Controller的方式非常多,是不统一的。以下就展示了三种方式的Controller定义,当然我们用得最多的还是方式一。
//方式一:通过@Controller、@RequestMapping来定义,Handler对象为HandlerMethod
@Controller
public class DemoController {
@RequestMapping("/getUserName")
public ModelAndView getUserName() {
ModelAndView model = new ModelAndView("Greeting");
model.addObject("message", "TOM");
return model;
}
}
//方式二:实现Controller接口 + xml配置文件:配置DemoController与URL的对应关系
public class DemoControllerImpl implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView model = new ModelAndView("HelloWorld");
model.addObject("message", "HelloWorld");
return model;
}
}
//方式三:继承HttpServlet抽象类 + xml配置文件:配置DemoExtendServletController类与URL的对应关系
public class DemoExtendServletController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello World!");
}
}
方式一中的函数的定义很随意、不固定,方式二中的函数定义是 handleRequest()、方式三中的函数定义是HttpServlet的service()方法。如果不作适配,DispatcherServlet需要根据不同类型的Controller,调用不同的函数,会有很多if-else分支判断,而且,如果要增加一个新的Controller的定义方法,我们就要在DispatcherServlet类代码中,对应地增加一段if逻辑,这显然不符合开闭原则。Spring利用适配器模式对代码进行改造,定义了统一的接口HandlerAdapter,并且对每种Controller定义了对应的适配器类。在DispatcherServlet类中,就不需要区分对待不同的Controller对象了,统一调用HandlerAdapter的handle()函数就可以了。