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不相等的話,則認爲是文件上傳的請求。
getHandler(processedRequest);
- 爲此請求返回HandlerExecutionChain。按順序嘗試所有的handler mapping
獲取當前請求對應的處理類,在這個處理鏈中會包含對應的攔截器的信息。HandlerExecutionChain這個類中包含變和不變量的兩部分內容
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
循環handlerMappings,然後獲取對應的執行鏈,只要找到一個對應的執行鏈就返回
SpringMVC默認加載三個請求處理映射類:
- RequestMappingHandlerMapping
- SimpleUrlHandlerMapping
- BeanNameUrlHandlerMapping
這三個類有一個共同的父類:AbstractHandlerMapping。
hm.getHandler(request)這個getHandler方法在AbstractHandlerMapping中,它的子類都沒有重寫這個方法。
AbstractHandlerMethodMapping#getHandler
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 兩個子類重寫該方法:AbstractHandlerMethodMapping和AbstractUrlHandlerMapping
Object handler = getHandlerInternal(request);
// 如果沒有找到的話,默認處理類
if (handler == null) {
handler = getDefaultHandler();
}
// 如果沒有默認的處理類,返回null
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
// 包裝爲執行器鏈
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
// 是不是跨域請求
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
AbstractHandlerMethodMapping#getHandlerInternal
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 得到請求 url 的查詢路徑
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
}
// 獲取併發讀鎖
this.mappingRegistry.acquireReadLock();
try {
// 得到查詢路徑對應的處理器方法
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
getHandlerExecutionChain
創建執行器鏈的內容:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//判斷handler是不是執行器鏈,如果不是創建一個執行器鏈
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//包裝攔截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
這個方法主要是創建執行器鏈,添加攔截器.
判斷
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
如果沒有找到對應的處理類的話,這裏通常會返回404,如果throwExceptionIfNoHandlerFound屬性值爲true的情況下會拋出異常。
我們繼續往下分析:
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
getHandlerAdapter
獲取處理適配器
- SimpleControllerHandlerAdapter
適配SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping的映射的,也就是實現Controller接口的Handler - AbstractHandlerMethodAdapter
適配RequestMappingHandlerMapping,也就是我們常用的RequestMapping註解 - HttpRequestHandlerAdapter
適配遠程調用的 - SimpleServletHandlerAdapter
適配Servlet實現類的
supports這個方法的實現都比較簡單,我們這裏就不細分析了
如果是GET請求,內容沒有變化則直接返回
applyPreHandle
- 應用已註冊的preHandle攔截方法
mv = ha.handle
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
執行handle
接續分析:
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
這兩段代碼的意思是:如果返回的ModelAndView不爲null,並且沒有設置view的話,這設置默認的view。處理攔截器的postHandle。
我們最後再看一下processDispatchResult這個方法:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
//如果有異常,則處理異常,返回異常頁面
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
//返回的ModelAndView不爲null
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//解析頁面
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
//調用處理攔截器的afterCompletion方法
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
在這個方法裏幹了這樣的幾件事:如果出現異常,返回異常頁面。如果沒有異常,ModelAndView不爲null,則正常渲染頁面,調用攔截器的afterCompletion方法。
我們對於doDispatch的分析,就先到這裏。