DispatcherServlet是前端控制器设计模式的实现,提供了Spring Web MVC的集中访问点, 而且负责职责的分派,而且与Spring Ioc容器无缝集成, 从而可以获的Spring的所有好处。
作用
DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:
文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析
通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器)
、通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器)
通过ViewResolver解析逻辑视图名到具体视图实现
本地化解析
渲染具体的视图等
如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。
DispatcherServlet的工作流程
DospatcherServlet实际上是一个Servlet(它继承HttpServlet)。DispatcherServlet处理的请求必须在同一个web.xml文件里使用url-mapping定义映射。这是标准的J2EE servlet配置。
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); mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. 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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); 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()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
下面我们分析一下这些代码的意思,先看这句代码:
processedRequest = checkMultipart(request);
检查这个请求是不是文件上传的请求的。我们具体的看一下它是怎么判断是否是文件上传的。
checkMultipart
转换请求到multipart请求,使multipart解析器可用。如果没有解析器被设置,只需使用现有的请求
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { } else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) { } else { return this.multipartResolver.resolveMultipart(request); } } return request; }
这里先是判断multipartResolver这个类是不是为空
multipartResolver是需要我们进行配置的,通常配置如下所示:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
如果没有配置MultipartResolver的话,则认为不是文件上传的请求,如果配置了MultipartResolver的话,调用isMultipart方法验证是不是文件上传的请求,isMultipart方法的内容如下:
public boolean isMultipart(HttpServletRequest request) { return (request != null && ServletFileUpload.isMultipartContent(request)); }
在这里我们看到了一个类是:ServletFileUpload文件上传工具包:commons-fileupload中的类。
SpringMVC对文件上传的处理是借助于commons-fileupload包来实现的。我们去isMultipartContent这个方法中看一下:
public static final boolean isMultipartContent( HttpServletRequest request) { if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) { return false; } return FileUploadBase.isMultipartContent(new ServletRequestContext(request)); }
这里首先判断一下是不是POST请求,如果不是POST请求直接返回false,接着通过isMultipartContent方法来继续验证:
public static final boolean isMultipartContent(RequestContext ctx) { String contentType = ctx.getContentType(); if (contentType == null) { return false; } if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) { return true; } return false; }
如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求。
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { } else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) { } else { return this.multipartResolver.resolveMultipart(request); } } return request; }
如果是文件上传请求,则继续判断这个请求是不是已经被转换为MultipartHttpServletRequest类型了。
在Spring-Web这个jar中有一个过滤器org.springframework.web.multipart.support.MultipartFilter
如果在web.xml中配置这个过滤器的话,则会在过滤器中提前判断是不是文件上传的请求,并将请求转换为MultipartHttpServletRequest类型。
这个过滤器中默认使用的MultipartResolver为StandardServletMultipartResolver。
如果不是MultipartHttpServletRequest类型的话,则判断是不是出现异常了。如果上面这两步返回的都是false,则会执行这句:this.multipartResolver.resolveMultipart(request),将请求转换为MultipartHttpServletRequest类型。我们具体看一下:
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException { Assert.notNull(request, "Request must not be null"); if (this.resolveLazily) { return new DefaultMultipartHttpServletRequest(request) { @Override protected void initializeMultipart() { MultipartParsingResult parsingResult = parseRequest(request); setMultipartFiles(parsingResult.getMultipartFiles()); setMultipartParameters(parsingResult.getMultipartParameters()); setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes()); } }; } else { MultipartParsingResult parsingResult = parseRequest(request); return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes()); } }
在CommonsMultipartResolver中有一个属性叫resolveLazily,这个属性值代表是不是延迟解析文件上传,默认为false。最终返回的是一个DefaultMultipartHttpServletRequest的类。这里有一个重要的方法是:parseRequest,这个方法干的事是解析文件上传请求。它的底层是commons-fileupload那一套,不同的是Spring在获取FileItem之后,又进行了一下封装,封装为便于Spring框架整合。
下面我们接着看这一句话:
multipartRequestParsed = (processedRequest != request);
processedRequest是checkMultipart(request)这个方法返回的值,如果processedRequest和request不相等的话,则认为是文件上传的请求。