前文分析了DispatcherServlet的初始化流程之后,现在我们来探究一下DispatcherServlet是如何处理请求的。同样还是那三个Servlet类开始:HttpServletBean、FrameworkServlet和DispatcherServlet。
HttpServletBean
HttpServletBean只参与初始化工作并不参与请求的处理。
FrameworkServlet
Servlet的处理流程我们知道处理请求的入口是service方法。首先从Servlet接口的service方法开始, 然后在HttpServlet的service方法中根据请求的类型不同将请求路由到了doGet、doHead、doPost等7个方法中,其中doHead、doOptions、doTrace做了默认实现。
FrameworkServlet重写了service、doGet、doPost、doPut、doDelete、doOptions、doTrace方法。在service方法中增加了对PATCH类型的处理,其他类型的请求调用super.service
直接交给了父类进行处理; doOptions和doTrace方法可以通过设置 dispatchOptionsRequest 和 dispatchTraceRequest 参数决定是自己处理还是交给父类处理(默认都是交给父类处理, doOptions 会在父类的处理结果中增加 PATCH 类型);doGet、 doPost、 doPut 和 doDelete 都是自己处理。 所有需要自己处理的请求都交给processRequest
方法进行统一处理。
//FrameworkServlet
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
再来看下每个方法里最主要方法processRequest
//FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 获取上一个请求保存的LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//建立新的LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//获取上一个请求保存的RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//建立新的RequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
//异步请求,可以先跳过,后面文章再说
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
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 {
//恢复
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
//发布ServletRequestHandledEvent消息
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
从上面的流程我们知道这个方法大致在做两件事:
LocaleContext
、RequestAttributes
的设置与恢复- 处理完后发布
ServletRequestHandledEvent
类型消息
LocaleContext
可以用于获取locale
,RequestAttributes
用于管理request
和session
属性。如果我们在service层想要获取locale
,常见的办法可能是将request传递到service层,但是其实有更简洁的办法就是直接通过LocaleContextHolder#getLocale()
来获取。同样RequestContextHolder
里面封装了ServletRequestAttributes
,我们可以get到request
,session
,response
,因为是对象的引用,所以即使在doService
方法里设置了Attribute我们同样也可以拿到属性!
你可能会疑惑为什么后面又执行resetContextHolders
,这是因为在servlet外部可能有其他的操作比如Filter,为了不影响那些操作,所以需要恢复。
当publishEvents
被设置为true的时候,请求结束就会发出这个消息,无论请求是否处理成功都会发布,publishEvents
字段可以通过配置改变。我们可以通过监听这个事件来做一些事,比如记录日志等。
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
long startTime, @Nullable Throwable failureCause) {
if (this.publishEvents && this.webApplicationContext != null) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause, response.getStatus()));
}
}
DispatcherServlet
我们从上面的分析得知这里的入口是doService
,但是看代码的处理他其实又交由给doDispatch(request, response);
方法处理。进入doDispatch
方法前在doService
方法前后都做了一些处理:首先判断是不是include请求, 如果是则对request的Attribute 做个快照备份, 等doDispatch
处理完之后(如果不是异步调用且未完成)进行还原, 在做完快照后又对request设置了 一些属性:
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
//是include请求进行request的attributes快照备份
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
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());
if (this.flashMapManager != null) {
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()) {
//还原快照内容
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
对 request 设置的属性中, 前面 4 个属性 webApplicationContext
、localeResolver
、themeResolver
和 themeSource
在之后介绍的 handler 和 view 中需要使用, 到时候再作分析。 后面三个属性都和 flashMap 相关, 主要用于Redirect 转发时参数的传递。
doDispatch方法我们通过剪去细枝末节(比如卸载multipart内容), 保留主要流程:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.ModelAndView->mv
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
它们的任务分别是:
- 根据request找到Handler;
- 根据Handler找到对应的 HandlerAdapter;
- 用HandlerAdapter处理Handler;
- 调用 processDispatchResult方法处理上面处理之后的结果(包含找到View 并渲染输出给用户),
这里需要解释三个概念: HandlerMapping、 Handler和HandlerAdapter:
- Handler: 也就是处理器,它直接对应着MVC中的C也就是Controller层, 它的具体表现形式有很多, 可以是类,也可以是方法,如果你能想到别的表现形式也可以使用, 它的类型是Object。
@RequestMapping
的所有方法都可以看成一个Handler。只要可以实际处理请求就可以是Handler。 - HandlerMapping : 是用来查找Handler的,在Spring MVC中会处理很多请求,每个请求都需要一个Handler来处理, 具体接收到一个请求后使用哪个Handler来处理呢?这就是HandlerMapping要做的事情。
- HandlerAdapter: 从名字上就可以看出它是一个Adapter, 也就是适配器。 因为Spring MVC中的Handler可以是任意的形式,只要能处理请求就OK, 但是 Servlet需要的处理方法的结构却是固定的,都是以 request和response为参数的方法(如doService方法)。 怎么让固定的 Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
现在我们再来详细看完整的代码
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 {
//判断是不是Multipart类型的请求,即文件上传
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 根据request找到handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 根据Handler找到对应的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理GET HEAD请求的last-modified
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 执行Interceptor的prehandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// HandlerAdapter使用Handler处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果有异步处理,直接返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//当view为空时(比如Hanler是void类型),根据request设置默认view
applyDefaultViewName(processedRequest, mv);
//执行Interceptor的postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//处理返回结果。包括处理异常,渲染页面,发出完成通知触发interceptor的afterCompletion
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()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 文件处理完成,删除上传请求资源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
关于Last-Modified:
当浏览器第一次请求一个资源时,服务端返回状态码200,返回请求的资源的同时HTTP响应头会有一个Last-Modified标记着文件在服务端最后被修改的时间。
浏览器第二次请求上次请求过的资源时,浏览器会在HTTP请求头中添加一个If-Modified-Since的标记,用来询问服务器该时间之后文件是否被修改过
如果服务器端的资源没有变化,则自动返回304状态,使用浏览器缓存,从而保证了浏览器不会重复从服务器端获取资源,也保证了服务器有变化时,客户端能够及时得到最新的资源.
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//请求处理过程出现异常,处理异常
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 渲染页面
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// 启动异步则返回
return;
}
//发出请求处理完成的通知,触发Interceptor的afterCompletion
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
可以看到processDispatchResult
处理异常的方式 其实就是将相应的错误页面设置到View, 在其中的processHandlerException
方法中用到了HandlerExceptionResolver
渲染页面具体在render
方法中执行,render
中首先对response设置了locale, 过程中使用到了LocaleResolver
, 然后判断 View如果是String
类型则调用resolveViewName
方法使用 ViewResolver
得到实际的View
, 最后调用View
的render
方法对页面进行具体渲染, 渲染的过程中使用到了ThemeResolver
。
小结
HttpServletBean: 没有参与实际请求的处理。
FrameworkServlet : 将不同类型的请求合并到了processRequest方 法统一处理:processRequest
方法中做了三件事:
1.调用了doService
模板方法具体处理请求;
2.将当前请求的LocaleContext
和ServletRequestAttributes
在处理请求前设置到了LocaleContextHolder
和RequestContextHolder
, 并在请求处理完成后恢复;
3.请求处理完后发布了ServletRequestHandledEvent
消息。
DispatcherServlet : 实现了doService
方法,给request设置了一些属性并将请求交给doDispatch方法具体处理。DispatcherServlet中的doDispatch
方法 完成Spring MVC中请求处理过程的顶层设计,它使用DispatcherServlet中的九大组件完成了具体的请求处理 。
参考
《看透springMvc源代码分析与实践》