本文主要講解 SpringMVC 執行過程,並針對相關源碼進行解析。
首先,讓我們從 Spring MVC 的四大組件:前端控制器(DispatcherServlet)、處理器映射器(HandlerMapping)、處理器適配器(HandlerAdapter)以及視圖解析器(ViewResolver) 的角度來看一下 Spring MVC 對用戶請求的處理過程,過程如下圖所示:
SpringMVC 執行過程
- 用戶請求發送到前端控制器 DispatcherServlet。
- 前端控制器 DispatcherServlet 接收到請求後,DispatcherServlet 會使用 HandlerMapping 來處理,HandlerMapping 會查找到具體進行處理請求的 Handler 對象。
- HandlerMapping 找到對應的 Handler 之後,並不是返回一個 Handler 原始對象,而是一個 Handler 執行鏈(HandlerExecutionChain),在這個執行鏈中包括了攔截器和處理請求的 Handler。HandlerMapping 返回一個執行鏈給 DispatcherServlet。
- DispatcherServlet 接收到執行鏈之後,會調用 Handler 適配器去執行 Handler。
- Handler 適配器執行完成 Handler(也就是 Controller)之後會得到一個 ModelAndView,並返回給 DispatcherServlet。
- DispatcherServlet 接收到 HandlerAdapter 返回的 ModelAndView 之後,會根據其中的視圖名調用 ViewResolver。
- ViewResolver 根據邏輯視圖名解析成一個真正的 View 視圖,並返回給 DispatcherServlet。
- DispatcherServlet 接收到視圖之後,會根據上面的 ModelAndView 中的 model 來進行視圖中數據的填充,也就是所謂的 視圖渲染。
- 渲染完成之後,DispatcherServlet 就可以將結果返回給用戶了。
在瞭解了大概的執行過程後,讓我們一起去從源碼角度去深入探索(SpringMVC 版本為 5.2.3):
我們先創建一個 Controller 以便進行 debug,內容如下:
<code>@Controller
public class SpringMvcController {
@RequestMapping("testSpringMvc")
public String testSpringMvc(Map<string> map) {
map.put("note", "在看轉發二連");
return "success";
}
}/<string>/<code>
然後再創建一個 html 文件,採用 Thymeleaf 模版引擎,文件內容如下:
<code>
<title>在看轉發二連/<title>
/<code>
好了,然後啟動程序,讓我們訪問 http://localhost:8080/testSpringMvc,來一步一步探索 SpringMVC 的執行過程:
源碼解析
首先當我們訪問頁面的時候,將會把請求發送到前端控制器 DispatcherServlet,DispatcherServlet 是一個 Servlet,我們知道在 Servlet 在處理一個請求的時候會交給 service 方法進行處理,這裡也不例外,DispatcherServlet 繼承了 FrameworkServlet,首先進入 FrameworkServlet 的 service 方法:
<code>protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 請求方法
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
// 若方法為 PATCH 方法或為空則單獨處理
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {// 其他的請求類型的方法經由父類,也就是 HttpServlet 處理
super.service(request, response);
}
}/<code>
HttpServlet 中會根據請求類型的不同分別調用 doGet 或者 doPost 等方法,FrameworkServlet 中已經重寫了這些方法,在這些方法中會調用 processRequest 進行處理,在 processRequest 中會調用 doService 方法,這個 doService 方法就是在 DispatcherServlet 中實現的。下面就看下 DispatcherServlet 中的 doService 方法的實現。
DispatcherServlet 收到請求
DispatcherServlet 中的 doService方法:
<code>protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// 給 request 中的屬性做一份快照,以便能夠恢復原始屬性
Map<string> 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));
}
}
}
// 如果沒有配置本地化或者主題的處理器之類的,SpringMVC 會使用默認的配置文件,即 DispatcherServlet.properties
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);
}
}
}
}/<string>/<code>
接下來 DispatcherServlet 開始真正的處理,讓我們來看下 doDispatch 方法,首先會獲取當前請求的 Handler 執行鏈 ,然後找到合適的 HandlerAdapter(此處為 RequestMappingHandlerAdapter),接著調用 RequestMappingHandlerAdapter 的 handle 方法,如下為 doDispatch 方法:
<code>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 類型的,比如上傳等;如果是 Multipart 類型的,則轉換為 MultipartHttpServletRequest 類型
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 獲取當前請求的 Handler 執行鏈
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 獲取當前請求的 Handler 適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 對於 header 中 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;
}
// 實際調用 Handler 的地方
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 處理成默認視圖名,也就是添加前綴和後綴等
applyDefaultViewName(processedRequest, mv);
// 攔截器postHandle方法進行處理
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);
}
}
}
}/<code>
查找對應的 Handler 對象
讓我們去探索下是如何獲取當前請求的 Handler 執行鏈,對應著這句代碼 mappedHandler = getHandler(processedRequest);,看下 DispatcherServlet 具體的 getHandler 方法,該方法主要是遍歷所有的 handlerMappings 進行處理,handlerMappings 是在啟動的時候預先註冊好的,handlerMappings 包含 RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、RouterFunctionMapping、SimpleUrlHandlerMapping 以及 WelcomePageHandlerMapping,在循環中會調用 AbstractHandlerMapping 類中的 getHandler 方法來獲取 Handler 執行鏈,若獲取的 Handler 執行鏈不為 null,則返回當前請求的 Handler 執行鏈,DispatcherServlet 類的 getHandler 方法如下:
<code>protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍歷所有的 handlerMappings 進行處理,handlerMappings 是在啟動的時候預先註冊好的
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}/<code>
在循環中,根據 mapping.getHandler(request);,繼續往下看 AbstractHandlerMapping 類中的 getHandler 方法:
<code>public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 根據 request 獲取 handler
Object handler = getHandlerInternal(request);
if (handler == null) {
// 如果沒有找到就使用默認的 handler
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// 如果 Handler 是 String,表明是一個 bean 名稱,需要尋找對應 bean
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 封裝 Handler 執行鏈
return getHandlerExecutionChain(handler, request);
}/<code>
AbstractHandlerMapping 類中的 getHandler 方法中首先根據 requrst 獲取 handler,主要是調用了 AbstractHandlerMethodMapping 類中的 getHandlerInternal 方法,該方法首先獲取 request 中的 url,即 /testSpringMvc,用來匹配 handler 並封裝成 HandlerMethod,然後根據 handlerMethod 中的 bean 來實例化 Handler 並返回。
<code>protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 獲取 request 中的 url,用來匹配 handler
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
// 根據路徑尋找 Handler,並封裝成 HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
// 根據 handlerMethod 中的 bean 來實例化 Handler,並添加進 HandlerMethod
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}/<code>
接下來,我們看 lookupHandlerMethod 的邏輯,主要邏輯委託給了 mappingRegistry 這個成員變量來處理:
<code>protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<match> matches = new ArrayList<>();
// 通過 lookupPath 屬性中查找。如果找到了,就返回對應的RequestMappingInfo
ListdirectPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); /<match>/<code>
if (directPathMatches != null) {
// 如果匹配到了,檢查其他屬性是否符合要求,如請求方法,參數,header 等
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 沒有直接匹配到,則遍歷所有的處理方法進行通配符匹配
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
// 如果方法有多個匹配,不同的通配符等,則排序選擇出最合適的一個
Comparator<match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
// 如果有多個匹配的,會找到第二個最合適的進行比較
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
// 不能有相同的最優 Match
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
// 設置 request 參數(RequestMappingHandlerMapping 對其進行了覆寫)
handleMatch(bestMatch.mapping, lookupPath, request);
// 返回匹配的 url 的處理的方法
return bestMatch.handlerMethod;
}
else {
// 調用 RequestMappingHandlerMapping 類的 handleNoMatch 方法再匹配一次
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}/<match>
通過上面的過程,我們就獲取到了 Handler,就開始封裝執行鏈了,就是將我們配置的攔截器加入到執行鏈中去,getHandlerExecutionChain 方法如下:
<code>protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 如果當前 Handler 不是執行鏈類型,就使用一個新的執行鏈實例封裝起來
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
// 遍歷攔截器,找到跟當前 url 對應的,添加進執行鏈中去
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;
}/<code>
到此為止,我們就獲取了當前請求的 Handler 執行鏈,接下來看下是如何獲取請求的 Handler 適配器,主要依靠 DispatcherServlet 類的 getHandlerAdapter 方法,該方法就是遍歷所有的 HandlerAdapter,找到和當前 Handler 匹配的就返回,在這裡匹配到的為 RequestMappingHandlerAdapter。DispatcherServlet 類的 getHandlerAdapter 方法如下:
<code>protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
// 遍歷所有的 HandlerAdapter,找到和當前 Handler 匹配的就返回
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}/<code>
HandlerAdapter 執行當前的 Handler
再獲取完當前請求的 Handler 適配器後,接著進行緩存處理 ,也就是對 last-modified 的處理,然後調用 applyPreHandle 方法執行攔截器的 preHandle 方法,即遍歷所有定義的 interceptor,執行 preHandle 方法,然後就到了實際執行 handle 的地方,doDispatch 方法中 handle 方法是執行當前 Handler,我們這裡使用的是 RequestMappingHandlerAdapter,首先會進入 AbstractHandlerMethodAdapter 的 handle 方法:
<code>public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}/<code>
在 AbstractHandlerMethodAdapter 的 handle 方法中又調用了 RequestMappingHandlerAdapter 類的 handleInternal 方法:
<code>protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 執行方法,封裝 ModelAndView
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}/<code>
在執行完 handle 方法後,然後調用 applyDefaultViewName 方法組裝默認視圖名稱,將前綴和後綴名都加上,接著調用 applyPostHandle 方法執行攔截器的 preHandle 方法,也就是遍歷所有定義的 interceptor,執行preHandle 方法。
處理最終結果以及渲染
最後調用 DispatcherServlet 類中的 processDispatchResult 方法,此方法是處理最終結果的,包括異常處理、渲染頁面和發出完成通知觸發攔截器的 afterCompletion() 方法執行等,processDispatchResult()方法代碼如下:
<code>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) {
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;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}/<code>
接下來讓我們看下 DispatcherServlet 類的 render 方法是如何完成渲染的,DispatcherServlet 類的 render 方法渲染過程如下:
- 判斷 ModelAndView 中 view 是否為 view name,沒有獲取其實例對象:如果是根據 name,如果是則需要調用 resolveViewName 從視圖解析器獲取對應的視圖(View)對象;否則 ModelAndView 中使用 getview 方法獲取 view 對象。
- 然後調用 View 類的 render 方法。
DispatcherServlet 類的 render 方法如下:
<code>protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 設置本地化
Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// 解析視圖名,得到視圖
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 委託給視圖進行渲染
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}/<code>
因為我們用的是 Thymeleaf 模版引擎,所以 view.render 找到對應的視圖 ThymeleafView 的 render 方法進行渲染。
<code>public void render(final Map<string> model, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
renderFragment(this.markupSelectors, model, request, response);
}/<string>/<code>
ThymeleafView 的 render 方法又調用 renderFragment 方法進行視圖渲染,渲染完成之後,DispatcherServlet 就可以將結果返回給我們了。
也就是 note 對應的 value:在看轉發二連 。
總結
通過本文的源碼分析,我相信我們都能夠清楚的認識到 SpringMVC 執行流程,進一步加深對 SpringMVC 的理解。
參考
https://dwz.cn/DmzEGQcx
https://dwz.cn/MSB2GJKT
閱讀更多 武培軒 的文章