DispatcherServlet 分發流程
0 太長不看版
HTTPServlet
的Service
方法將請求按類進行分解- 主要是根據HTTP方法的類型調用
doXXX
方法 - GET 和 HEAD 方法需要對 if-modified-since 進行特殊處理,其他是直接調用
- 主要是根據HTTP方法的類型調用
FrameworkServlet
重寫doXXX
方法,統一調用doService
方法doXXX
方法統一調用processRequest
方法doOptions
和doTrace
有額外的處理- 其他是直接調用
processRequest
主要是初始化ThreadLocal
,調用doService
方法,並進行日誌等處理ThreadLocal
是LocalContext
和Attributes
doService
方法執行核心邏輯,是抽象方法- 完成後會清空
ThreadLocal
,列印日誌,產生事件。
DispatcherServlet
進行具體的實現- 重寫
doService
方法- 添加
DispatcherServlet
特有的請求屬性 - 對 HTML 的 include 請求進行處理
- 對重定向的請求進行處理
- 將請求轉交給
doDispatch
方法進行實際的分發
- 添加
doDispatch
方法的邏輯為:- 查找是否有合適的 Handler,該過程在基於RESTful API 設計的 SpringMVC 中有性能問題
- 查找 Handler 是否有支援的 Adapter
- 執行攔截器
- 執行處理
- 解析結果並返回
- 重寫
1,DispatcherServlet
的父類做了什麼
DistpathcerServlet
的類圖如下,可見其父類為 FrameworkServlet
,同時是一個 HttpServlet
.
1.1 HttpServlet 的分發邏輯:
service
方法作為入口, 其邏輯如下:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
可見對於 POST
PUT
DELETE
OPTIONS
TRACE
方法都是直接調用對應的具體方法 doXXX
, 而 GET
方法會增加 if-modified-since
檢查,符合條件後才進行 doGet
, 這是為了支援 HTTP 協議的 if-modified-since
請求頭。而 HEAD 方法的額外處理也是檢查是否需要設置 last-modified
屬性。
HTTP 協議的
if-modified-since
請求是條件請求,要求返回的資源在指定日期後發生過修改。如果發生修改,則返回 200 OK 以及對應資源,否則返回 304 Not Modified.
1.2 FrameworkServlet 做了什麼
1.2.1 重寫 doXXX 方法
FrameworkServlet
沒有修改 HttpServlet
的分發邏輯,而是將所有的 doXXX
方法調用了同一方法 processRequest
。
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
doOptions
和 doTrace
方法進行了一些額外處理:
1.2.1.1 doOptions
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 如果允許轉發,並且是 CORS 請求,那麼檢查它是否是 pre-flight 預檢請求
if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
processRequest(request, response);
if (response.containsHeader("Allow")) {
// Proper OPTIONS response coming from a handler - we're done.
// 如果返回值是 "Allow",即方法允許,那就直接返回
return;
}
}
// 否則使用 HttpServlet 默認的方法檢查是否允許
// Use response wrapper in order to always add PATCH to the allowed methods
super.doOptions(request, new HttpServletResponseWrapper(response) {
@Override
public void setHeader(String name, String value) {
// 如果結果是 "Allow", 那麼以指定的格式返回
if ("Allow".equals(name)) {
value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
}
super.setHeader(name, value);
}
});
}
doOptions
方法例外在 OPTIONS 方法由多個來源,而 Tomcat 只處理來自 CORS 的預檢命令。對於不接受 CORS 的 Servlet 或其他來源的 OPTIONS 請求,就調用默認的方法實現。
1.2.1.2 doTrace
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.dispatchTraceRequest) {
processRequest(request, response);
if ("message/http".equals(response.getContentType())) {
// Proper TRACE response coming from a handler - we're done.
return;
}
}
super.doTrace(request, response);
}
doTrace 方法就簡單很多,它只需判斷是否已經處理了命令,如果沒有,則調用默認的方法。
為什麼不直接用
processRequest
重寫service
方法?
除了滿足里氏替換原則以外,根據 1.1 的分析,我們也可以看到,service
方法中處理了快取機制,即last-modified
屬性和if-modified-since
屬性的相關處理,以及 OPTIONS 和 TRACE 方法所需的一些額外處理
1.2.2 processRequest
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 分配變數
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 初始化上下文,實際上是把後兩個參數用 ThreadLocal 快取起來
initContextHolders(request, localeContext, requestAttributes);
// 執行真正的業務邏輯 doService
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 是執行緒專用的,在執行緒池場景下需要清空,否則會影響下一次使用。
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
// 輸出日誌
logResult(request, response, failureCause, asyncManager);
// 輸出事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
processRequest
主要是進行封裝和異常處理,並調用 doService
方法進行核心實現。而 doService
是一個抽象方法, DispatcherServlet
就實現了這一方法。
2 DispatcherServlet 的分發流程
2.1 doService 方法
doService
方法的文檔注釋為:暴露 DispatcherServlet
特有的請求屬性,並將請求轉交給 doDispatch
進行實際的分發
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
// 如果是 include 請求,保存請求的一個快照,以在 include 中保存原始屬性
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.
// 將框架對象暴露給 handler(controller)和 VO
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());
// 通過 FlashMap 恢復重定向請求的屬性
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);
}
// 獲取之前的 Path,並將當前 Path 添加到屬性中
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
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);
}
}
if (this.parseRequestPath) {
// 恢復 RequestPath
/* 該方法的邏輯為:如果 previousRequestPath 不為空,則將 request 的 PATH_ATTRIBUTE
屬性設為 previousRequestPath, 否則刪除 PATH_ATTRIBUTE 屬性
*/
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
該方法的功能是:
- 添加
DispatcherServlet
特有的請求屬性 - 對 HTML 的 include 請求進行處理
- 對重定向的請求進行處理
- 將請求轉交給
doDispatch
方法進行實際的分發
HTML 的 include 會引入另一個頁面。這裡採用了快照技術,將原始屬性存放到快照中,並在處理完成後恢復原屬性,以避免 include 後對原頁面產生影響。
FlashMap 將一個 Request 的屬性存儲,並存放在 Session 中,這樣如果發生了重定向,新產生的 Request 就可以從 flashMapManager 中獲取前一個請求的屬性(INPUT_FLASH_MAP_ATTRIBUTE)
2.2 doDispatch 方法
@SuppressWarnings("deprecation")
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 {
// 通過 multipartResolver 判斷是否是文件的多部分請求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 獲取 handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 獲取 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// 進行 last-modified 處理
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 檢查在 HandlerExecutionChain 中被註冊的攔截器,調用 preHandle 方法,返回值為是否放行
// 內部使用了責任鏈模式
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 最終執行 handler,並獲取 ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 如果返回的是 Model(即沒有 View ),那麼使用默認的 ViewName
applyDefaultViewName(processedRequest, mv);
// 執行攔截器的 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.
// 從 Spring 4.3 起,handler 發生的 Error 也會被處理
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 處理分發的結果,即解析後的 View 對象
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
// 出現異常,則需要繼續執行完成攔截器的 afterCompletion 方法
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 {
// Clean up any resources used by a multipart request.
// 如果是同步執行,則所有攔截器已經結束,這裡把 MultiPart 請求過程中佔用的文件全部釋放
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
doDispatch
方法的邏輯為:
- 查找是否有合適的 Handler
- 查找 Handler 是否有支援的 Adapter
- 執行攔截器
- 執行處理
- 解析結果並返回
MultipartResolver 解析請求是否是以允許的方法請求多部分文件,用於文件上傳
3 相關問題
3.1 RESTful API 的性能問題
問題來源參見:SpringMVC RESTful 性能優化
在 doDispatch
方法查找是否有合適的 Handler 的調用鏈為:
DispatcherServlet::doDispatch
-> DispatcherServlet::getHandler
-> AbstractHandleMapping::getHandle
-> AbstractHandlerMethodMapping::getHandlerInternal
-> AbstractHandlerMethodMapping::lookupHandlerMethod
源碼為:
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 查詢是否有直接匹配
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
// 如果有直接匹配,那麼就將結果快取
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
// 如果沒有直接匹配,或者直接匹配的快取結果為空,那麼就將所有的記錄加入
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
// 如果此時有了匹配,那就查找
Match bestMatch = matches.get(0);
// 如果匹配的 match 不止 1 個,那麼就檢查模糊性
if (matches.size() > 1) {
// 如果有超過一個匹配,則排序
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
// 日誌 TRACE 級
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
// 檢查模糊性
if (CorsUtils.isPreFlightRequest(request)) {
// 如果是 CORS 的預檢命令,檢查是否有 match 含有 CORS 配置
for (Match match : matches) {
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
}
else {
// 如果不是 CORS 預檢命令,檢查次佳匹配是否和最佳匹配同等最佳
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.getHandlerMethod();
}
else {
// 如果此時 matches 依然為空,那就直接返回
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
AbstractHandlerMethodMapping::lookupHandlerMethod
中的查找邏輯為:
- 查找
mappingRegistry
中是否快取有該 URL 的結果快取。如果有,那就將快取直接加入matches
中 - 如果沒有直接匹配,或者直接匹配的快取結果為空,那麼就將將所有註冊的映射加入
matches
中,並遍歷檢查是否匹配 - 如果
matches
為空,直接返回handleNoMatch
- 否則,檢查最佳匹配是否唯一,如果唯一,則返回最佳匹配的 Handler, 否則返回模糊匹配錯誤
由於在 RESTful API 中,會有大量相似的 URL,第二步中的遍歷將不得不使用正則表達式匹配,而正則表達式匹配的時間複雜度是很高的。因此當 RESTful API 不斷增長的時候,性能也會不斷變差。
解決方案就是SpringMVC RESTful 性能優化中提到的繼承 AbstractHandlerMethodMapping
類並重寫其匹配邏輯,然後替換掉 SpringMVC 的默認組件。