文章目錄
在上一篇文章中我們講到了SpringMVC的初始化,分別初始化兩個ApplicationContext,並且初始化一些處理器,然后將url與Controller的method映射關系存放起來。在這篇文章中我們將深入源碼分析SpringMVC是怎樣處理一個請求的、Adapter是干什么用的、以及攔截器的原理。
1. DispatcherServlet處理請求
我們知道,在web.xml中配置的DispatcherServlet 映射的url是全部,也就是說,所有的請求都將由這個servlet去處理,所以,我們來關注一下DispatcherServlet的doGet與doPost方法(位於父類FrameworkServlet中實現):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
} |
由此看出,這兩個方法都一致指向同一個方法processRequest:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//將當前線程的LocaleContext對象提取出來
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//根據request創建一個LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//將當前線程的RequestAttributes對象提取出來
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//根據request創建一個RequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//將上述創建的兩個對象綁定到當前線程
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();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
//對ApplicationContext發布事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
} |
這個方法還沒有做正式的處理請求的邏輯,而是在做一些准備工作:
- 提取當前線程的LocaleContext、RequestAttributes對象
- 根據request創建RequestAttributes、LocaleContext對象,並將這兩個對象綁定到當前線程
- 處理請求
- 將剛剛提取的兩個對象綁定到當前線程
- 發布事件
這里我們需要關注的是doService方法(由DispatcherServlet實現):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(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請求,如果是,保存當前request屬性的快照
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
//將一些處理器屬性賦值給request,在后面會用到
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 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()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
//如果是include請求,根據快照屬性進行恢復
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
} |
這個方法中依然是在做一些准備工作,包括是否是include的判斷和操作,設置一些處理器在request中,到最后才開始真正做請求的處理doDispatch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
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);
// Determine handler for the current request.
//取得處理當前請求的hanlder處理器
//第一個步驟的意義就在這里體現了.這里並不是直接返回controller
//而是返回的HandlerExecutionChain請求處理器鏈對象
//該對象封裝了handler和interceptors
mappedHandler = getHandler(processedRequest);
// 如果handler為空,則返回404
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//獲取處理request的處理器適配器handler adapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
//處理 last-modified 請求頭
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
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;
}
//如果mv中沒有設置視圖並且mv為空,給一個默認的視圖處理(如果有)
applyDefaultViewName(processedRequest, mv);
//攔截器的后處理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
//如果發生異常,異常對象賦值給dispatchException
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
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//如果參數dispatchException不為空,證明發生了異常,將進行異常的處理(視圖)
//之后會根據mv跳轉視圖
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//觸發完成之后的操作,回調攔截器的afterCompletion方法,不同的是,ex不為空
//也就是說會傳一個異常進去
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
//觸發完成之后的操作,回調攔截器的afterCompletion方法,不同的是,ex不為空
//也就是說會傳一個異常進去
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
//在最后,又提供了一個回調入口,如果攔截器有AsyncHandlerInterceptor類型的話
//將執行AsyncHandlerInterceptor的afterConcurrentHandlingStarted回調
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
} |
在這個方法中,我們羅列出主要的主線:
- 根據request信息尋找對應的handler
- 根據Handler尋找對應的HanldlerAdapter
- 過濾器的處理
- 異常視圖的處理
- 根據視圖跳轉頁面
1.1 尋找Handler
首先,根據request去調用了getHandler方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//初始化的handlerMappings需不為空
if (this.handlerMappings != null) {
//遍歷
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
//碰到返回值不為空的,證明找到了,直接返回
if (handler != null) {
return handler;
}
}
}
return null;
} |
我們需要關注的是指定的HandlerMapping的getHandler是怎么找chain的,我們回憶一下上一篇文章中說到handlerMapping的初始化,其初始化了兩個handlerMapping,而我們只需要關注一個Handler那就是RequestMappingHandlerMapping,回憶一下它的類結構圖:
我們進入其抽象類的AbstractHandlerMapping的getHandler方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//由子類實現,也就是AbstractHandlerMethodMapping
Object handler = getHandlerInternal(request);
if (handler == null) {
//如果沒獲取到handler,獲取一個默認的
handler = getDefaultHandler();
}
//還沒獲取到,就返回null
if (handler == null) {
return null;
}
// Bean name or resolved handler?
//此時的hanlder有可能是一個字符串,也就是beanName
if (handler instanceof String) {
String handlerName = (String) handler;
//從IOC容器中獲取
handler = obtainApplicationContext().getBean(handlerName);
}
//封裝為一個chain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
} |
這個方法大致是做一些包裝工作,將得到的handler封裝為chain並返回,主要的邏輯處理還是在getHandlerInternal方法中,在其子類AbstractHanlderMethodMapping實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//截取用於匹配的url有效路徑
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock();
try {
//查找url對應的method
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
} |
這里主要方法是lookupHandlerMethod,其主要用url去尋找對應的HandlerMethod,
1 2 3 4 5 6 |
this.mappingRegistry.getMappingsByUrl(lookupPath);
@Nullable
public List<T> getMappingsByUrl(String urlPath) {
return this.urlLookup.get(urlPath);
} |
還記得urlLookup這個Map嗎,在DispatcherServlet中初始化所有的url與method的映射,其中信息就存放在4個Map中,urlLookup就是其中之一存放映射關系的Map,此處從Map中取出url對應的method,然后返回一個封裝對象HandlerMethod。
回到主線getHandler方法中,我們知道,現在執行了getHandlerInternal方法返回了一個HandlerMethod對象,然后將對其進行封裝:
1 |
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//如果不是chain類型,將handler作為構造函數創建一個chain實例
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
//獲得有效url路徑,用於匹配攔截器規則
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//獲取所有攔截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
//如果是MappedInterceptor類型的,按照規則看看當前攔截器是否需要加入攔截
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
//如果攔截器規則設置/*,表示攔截所有,也就是說url是什么都會加入
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
//該攔截器直接加入攔截器鏈
chain.addInterceptor(interceptor);
}
}
return chain;
} |
創建一個chain實例,handler作為初始值存入實例中。
遍歷攔截器,如匹配則將攔截器應用於該chain中,在后面會對匹配的這些攔截器進行攔截器的調用。
到此就結束了尋找Handler的過程,這里來總結一下,首先用RequestMappingHandlerMapping這個HandlerMapping去尋找handler,然后此類根據url去找Map中url對應的method,封裝為HandlerMethod對象,然后再將HandlerMethod對象封裝為chain對象,主要目的是加入攔截器鏈,然后返回這個chain就是我們最終需要尋找的hanlder了。
1.2 沒有找到Handler的處理
如果找到的chain為空,則:
1 2 3 4 |
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
//如果允許拋出異常,當沒有handler被找到
if (this.throwExceptionIfNoHandlerFound) {
//拋出沒找到handler的異常
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
//直接跳轉默認的NOT_FOUND頁面
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} |
如果這里有設置throwExceptionIfNoHandlerFound為true的話,將會拋出異常,為什么這么做呢?讀者需要看外部catch的邏輯,如果這里拋出異常則會被catch住,然后處理視圖時exception參數是會有值的,將進行異常的處理,在后面會詳細解釋,這樣做的目的就是為了可以自定義異常的視圖。默認參數為false,則會跳轉默認異常頁面。
1.3 根據Handler尋找Adapter
回到主線doDispather,接下來會根據hanlder尋找adapter:
1 |
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
//遍歷Adapter
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
//如果支持,直接返回
if (ha.supports(handler)) {
return ha;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
} |
這里回憶一下默認的Adapter實現有哪些:
1 2 3 |
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter |
其中前兩個Adapter都跟我們當前的例子沒關系,怎么看出來的呢?回到getHandlerAdapter方法中,接下來會遍歷Adapter列表,依此調用supports方法查詢與當前handler是否支持。其上兩個Adapter一個是處理HttpRequestHandler,一個是處理Controller類的,只有最后一個RequestMappingHandlerAdapter的supports方法才是我們要的:
1 2 3 4 |
@Override
public boolean supports(Object handler) {
return HandlerMethod.class.equals(handler.getClass());
} |
還記得handler的類型嗎,在上面就已經被封裝成HandlerMethod對象了,所以此時是RequestMappingHandlerAdapter這個適配器來處理請求。
1.4 攔截器的處理
在處理請求的過程中,涉及到許多攔截器的處理,其中主要的攔截器是HandlerInterceptor這個接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//前置處理
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
//后置處理
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
//完成時觸發處理
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
} |
這個攔截器擁有三個回調入口:
- 前置處理:還未執行對應method的時候進入回調方法,如果方法返回false,將不會繼續往下執行了,直接return。
- 后置處理:在執行完method返回ModelAndView對象的時候,進入回調方法,注意這里還未進行視圖的跳轉,也就是說是在返回請求之前的時機,所以主要用途是可以修改ModelAndView,或是在request中賦值,這樣,后面進行跳轉的時候可以攜帶被自定義后處理修改后的ModelAndView跳轉到頁面去。
- 完成時觸發:這個回調入口在全部完成后(處理完視圖跳轉之后)會執行的回調方法,注意此時參數ex有可能不為空,若ex不為空用戶需要考慮是否進行異常的處理,因為ex如果不為空的話證明此時的處理模型和視圖的跳轉等一些流程出現了異常。若ex不為空,表示沒有異常執行方法完成。
來看看具體的回調入口是在什么時機吧。我們回到主線,此時已經尋找到Adapter了,之后將執行這段代碼(其中mappedHandler為之前我們獲得的chain,其包含了攔截器):
1 2 3 |
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
//獲取所有攔截器
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
//依次調用攔截器preHandle的方法,若返回false,則方法返回false
//並執行triggerAfterCompletion完成觸發回調,因為這里已經被攔截了,不會再執行下去了
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
} |
可以看到,前置處理主要是用來判斷該請求是否允許放行,若不允許,直接執行完成的觸發回調(在后面詳細解釋),只有全部返回true,才會進行下面的邏輯,這里假設所有攔截器都放行,我們繼續往下看:
1 2 3 4 5 6 7 8 9 10 11 |
//適配器執行HandlerMethod對應的方法,並返回一個ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//如果mv為空,視圖為空,給一個默認的視圖
applyDefaultViewName(processedRequest, mv);
//后處理
mappedHandler.applyPostHandle(processedRequest, response, mv); |
從流程上可以看出,這里是先執行了url對應的那個method,返回了一個mv,注意此時頁面都沒有跳轉,都沒有進行處理的時候,就進入了后處理方法applyPostHandle:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
//獲得所有的攔截器
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
//執行攔截器的postHandle后處理方法
interceptor.postHandle(request, response, this.handler, mv);
}
}
} |
從這里可以看出,后處理的時機是在頁面得到mv之前的,所以在后處理的時候最適合來處理ModelAndView的值,或是修改request中的屬性,在后面頁面處理的時候會拿到后處理修改之后的ModelAndView,起到了后處理ModelAndView的效果。
回到主線,繼續走:
1 2 3 4 5 6 7 8 9 10 |
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);
}
//處理MV,並且進行頁面的跳轉
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); |
這里注意兩個catch方法,若發生異常將執行完成觸發回調方法,將異常對象作為參數傳入此回調,來看看此完成回調方法做了什么:
1 2 3 4 5 6 7 8 9 |
private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
if (mappedHandler != null) {
//調用chain的triggerAfterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, ex);
}
throw ex;
} |
這里做回調,都是調用chain的方法,因為先前chain已經保存了需要執行的攔截器變量。這里進入triggerAfterCompletion方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
//回調入口
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
} |
沒有什么特別的,只是遍歷每個攔截器,執行攔截器的afterCompletion方法而已,值得一提的是,此時ex參數是不為空的,這里再舉一個例子,在doDspatcher方法中的處理分發結果processDispatchResult方法中,最后會執行這樣一段代碼:
1 2 3 |
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
} |
在處理完頁面跳轉之后,也是會執行triggerAfterCompletion完成時回調的,此時的ex是為null的,而回顧一下我們開頭說的前置處理,若返回false,直接執行完成觸發並直接中止請求的執行:
1 2 3 4 |
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
} |
可以看到,這里的ex參數也是null。也就是說,完成時觸發分別在以下場景會進行觸發:
- 前置處理器不放行,返回false時執行完成回調:ex=null
- 執行完頁面的跳轉之后,執行完成回調:ex=null
- 執行頁面跳轉的過程中若出現異常,執行完成回調:ex=當前出現的異常
所以,從以上情況可以看出此回調方法都是在被認為操作已經結束的時候執行的,其中的ex是有可能有值的,有沒有異常都會執行完成回調,所以在寫完成回調的時候建議考慮異常情況的邏輯處理,也就是ex != null的處理。
回到主線,最后,不管怎樣,都會進入一個finally語句塊:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
} |
這里介紹另一種攔截器,其是剛剛介紹的HandlerInterceptor攔截器的子類AsyncHandlerInterceptor,其又定義了一個方法,是在finally語句塊中會執行的一個回調入口,這里先看看AsyncHandlerInterceptor:
1 2 3 4 5 6 7 |
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
}
} |
接口只定義了一個方法,回到finally語句塊中,看看applyAfterConcurrentHandlingStarted方法做了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
//獲取chain中的攔截器
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
//若此攔截器是AsyncHandlerInterceptor類型的
if (interceptors[i] instanceof AsyncHandlerInterceptor) {
try {
AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptors[i];
//執行回調
asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
}
catch (Throwable ex) {
logger.error("Interceptor [" + interceptors[i] + "] failed in afterConcurrentHandlingStarted", ex);
}
}
}
}
} |
這段代碼很簡單,也就是說,如果我們的攔截器還額外實現了AsyncHandlerInterceptor接口,意味着其也實現了afterConcurrentHandlingStarted回調方法,在最后收尾時執行該回調。
1.5 Adapter處理請求
回到主線中去,接下來會使用得到的Adapter去執行handler方法(注意這里是執行的RequestMappingHandlerAdapter的handle方法,而RequestMappingHandlerAdapter不是webFlux包下的,webFlux里涉及到reactive,這里僅討論返回ModelAndView的情況)這里是RequestMappingHandlerAdapter的父類AbstractHandlerMethodAdapter實現了handle方法:
1 |
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); |
1 2 3 4 5 6 7 |
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
} |
而handleInternal方法是由RequestMappingHandlerAdapter實現的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
} |
到這里我並沒有去深究,因為其封裝了許多對象來處理,大致邏輯就是利用反射去執行對應的方法,值得一提的是參數的綁定,有兩種方式:
- 通過@RequestParam注解綁定
- 通過參數名稱綁定
第一種只需要在方法參數前面聲明注解@RequestParam(“paramName”),就可以將請求request傳來的參數名為"paramName"的參數綁定到方法的參數上。
第二種是可以不用寫注解,就可以將參數根據名稱綁定,SpringMVC底層用到了asm框架讀取字節碼文件來獲取參數的名稱,對方法參數進行綁定。
所以這里有一個可有可無的小建議,將參數都使用@RequestParam,這樣就省去了asm框架讀取字節碼的操作,也更能省點時間,不過這里參數的綁定上可能是有緩存的,所以這里留給讀者去探究。
1.6 異常視圖的處理
當處理請求時發生異常,Spring提供了一些方式來對異常的處理。這里回到主線doDispatcher,在processDispatchResult方法之前若出現任何異常,都會被catch捕捉,並對dispatchException進行賦值,並且正常執行processDispatchResult方法。在processDispatchResult方法中有這樣一段代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//當發生異常,exception!=null
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重新賦值,變為異常的mv視圖
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
} |
這里進入processHandlerException方法處理異常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
//使用handlerExceptionResolver處理異常
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
//返回值不為空,證明已經處理好,可以跳出循環往下執行了
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
} |
這里主要是handlerExceptionResolver處理的異常,handlerExceptionResolver也是一個DispatherServlet初始化的處理器,它與初始化HandlerMapping的過程一樣,如果沒有配置是會從配置文件中讀取默認配置的,讓我們來看看配置文件DispatcherServlet.properties:
1 2 3 |
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver |
而這三個Resolver都實現了AbstractHandlerExceptionResolver這個抽象類,所以不論是哪個resolver,都會進入此抽象類的resolveException方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
prepareResponse(ex, response);
//由子類實現
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
logException(ex, request);
}
return result;
}
else {
return null;
}
} |
而這里不同的方法是doResolveException,該方法返回一個ModelAndView對象,在方法內部對異常類型進行判斷,然后嘗試生成對應的ModelAndView對象,如果該方法返回了null,則Spring會繼續尋找其他的ExceptionResolver。這里doResolveException方法將會對ModelAndView設置一個異常視圖和一些屬性,並返回出去,從processDispatchResult可以看出,若是有異常,mv對象將會被異常處理返回的mv對象替換,並執行異常的mv的視圖的跳轉,從而優雅的解決異常問題。
1.7 頁面的跳轉
做了前面這么多的處理,現在來到最后也是最關鍵的一步,根據ModelAndView對象對頁面進行跳轉處理。
來到doDispatcher方法中的processDispatchResult方法,有這樣一段代碼:
1 2 3 4 5 6 7 8 |
//如果mv對象不為空
if (mv != null && !mv.wasCleared()) {
//跳轉
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
//視圖對象
View view;
//從mv直接嘗試拿視圖名稱
String viewName = mv.getViewName();
//如果視圖名稱不為空,證明需要尋找視圖對象
if (viewName != null) {
// We need to resolve the view name.
//根據視圖名稱,尋找視圖對象
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 {
// No need to lookup: the ModelAndView object contains the actual View object.
//名稱為空,那么mv就已經定義好了視圖對象
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() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
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 + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
} |
這里主要會對視圖名稱選擇合適的視圖對象進行渲染,然后進行頁面的跳轉,這里主要看resolveViewName方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
//使用視圖解析器去嘗試解析視圖
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
//如果返回值不為空,證明解析完成,直接返回視圖對象
if (view != null) {
return view;
}
}
}
return null;
} |
其中,viewResolver也是在上一篇講DispatherServlet的初始化時進行初始化的,同樣,其也是默認配置配置文件中的Resolver:
1 |
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver |
默認只有一個resolver,所以這里對InternalResourceViewResolver進行分析,其中resolveViewName方法在這個類的父類AbstractCachingViewResolver中定義:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
//判斷是否緩存,如果不緩存,直接創建一個視圖對象
return createView(viewName, locale);
}
//如果可以緩存
else {
//獲取緩存的key
Object cacheKey = getCacheKey(viewName, locale);
//根據key尋找對應視圖對象
View view = this.viewAccessCache.get(cacheKey);
//若沒找到
if (view == null) {
synchronized (this.viewCreationCache) {
//從第二層緩存取
view = this.viewCreationCache.get(cacheKey);
//還是沒找到
if (view == null) {
// Ask the subclass to create the View object.
//創建視圖對象
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
//默認不解決的視圖對象,空實現
view = UNRESOLVED_VIEW;
}
if (view != null) {
//放入兩個緩存中
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
//若是空實現,返回null
return (view != UNRESOLVED_VIEW ? view : null);
}
} |
這里關注創建視圖的方法createView,在子類UrlBasedViewResolver中定義:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
//處理前綴為"redirect:"的情況
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
//處理前綴為"forward:"的情況
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
} |
在創建視圖時分別考慮了幾種情況:
- 如果是redirect前綴:返回RedirectView類型的視圖對象
- 如果是forward前綴:返回InternalResourceView類型的視圖對象
- 如果不是上述的,調用父類創建視圖方法:
1 2 3 4
@Nullable protected View createView(String viewName, Locale locale) throws Exception { return loadView(viewName, locale); }其中loadView又回到其子類
UrlBasedViewResolver中:1 2 3 4 5 6
@Override protected View loadView(String viewName, Locale locale) throws Exception { AbstractUrlBasedView view = buildView(viewName); View result = applyLifecycleMethods(viewName, view); return (view.checkResource(locale) ? result : null); }進入buildView方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
protected AbstractUrlBasedView buildView(String viewName) throws Exception { Class<?> viewClass = getViewClass(); Assert.state(viewClass != null, "No view class"); AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass); //添加前綴與后綴 view.setUrl(getPrefix() + viewName + getSuffix()); String contentType = getContentType(); if (contentType != null) { //設置ContentType view.setContentType(contentType); } view.setRequestContextAttribute(getRequestContextAttribute()); view.setAttributesMap(getAttributesMap()); Boolean exposePathVariables = getExposePathVariables(); if (exposePathVariables != null) { view.setExposePathVariables(exposePathVariables); } Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes(); if (exposeContextBeansAsAttributes != null) { view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes); } String[] exposedContextBeanNames = getExposedContextBeanNames(); if (exposedContextBeanNames != null) { view.setExposedContextBeanNames(exposedContextBeanNames); } return view; }這里,前綴后綴是自己設置的,回憶一下xml配置,我們需要這樣配置一個視圖解析器:
1 2 3 4 5
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp"/> </bean>配置其前綴后綴,就在這里用到。
回到render方法,在獲取視圖對象之后,將調用此視圖對象的render方法,進行頁面的跳轉:
1 |
view.render(mv.getModelInternal(), request, response); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
//解析屬性
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//處理頁面跳轉
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
} |
這里進入的是抽象類AbstractView的render方法,首先回憶一下ModelAndView的使用,我們將屬性放入mv中,然后在JSP上可以使用JSTL語法或者request中獲取我們之前放入的屬性,以便在JSP中可以調用,而解析這些屬性的工作就是在createMergedOutputModel方法中完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
HttpServletRequest request, HttpServletResponse response) {
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = (this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
// Consolidate static and dynamic model attributes.
int size = this.staticAttributes.size();
size += (model != null ? model.size() : 0);
size += (pathVars != null ? pathVars.size() : 0);
Map<String, Object> mergedModel = new LinkedHashMap<>(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
if (model != null) {
mergedModel.putAll(model);
}
// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
}
return mergedModel;
} |
接着就是處理頁面的跳轉了,不同的視圖對象跳轉的方式都不一樣,具體邏輯在renderMergedOutputModel方法中定義,這里舉一個比較常用的RedirectView這個視圖對象:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws IOException {
String targetUrl = createTargetUrl(model, request);
targetUrl = updateTargetUrl(targetUrl, model, request, response);
// Save flash attributes
RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
// Redirect
sendRedirect(request, response, targetUrl, this.http10Compatible);
} |
無非就是使用Redirect重定向的方式去進行頁面的跳轉,當然每個不同的視圖對象都有不同的跳轉邏輯。
2.總結
本篇文章是繼上一篇分析SpringMVC的文章寫的,在上篇文章中講到了Loader與DispatcherServlet的一系列初始化工作,例如處理器的初始化,在本篇文章中大部分都有用到,所以上一篇文章初始化也是很重要的。
在本篇文章中,分析當一個請求request來了,交由DispathcerServlet處理,DispathcerServlet會先做一些准備工作,然后再尋找對應的Handler,去根據url尋找method對象,然后尋找此請求url對應的攔截器,將信息都封裝成一個chain,然后就去尋找handler對應的Adapter,因為處理的時候需要Adapter對象調用handler方法才可以對方法進行執行,就像一個電源適配器,需要轉換一下才可以用。然后還講到了攔截器的使用,以及攔截器的幾個方法的回調時機,回調入口分別在DisapathcerServlet中的哪些執行流程中。然后講到了異常視圖的處理,如何處理全局異常,並且跳轉到異常的視圖。最后也是最重要的,將根據ModelAndView對象去尋找視圖對象,然后視圖對象進行跳轉操作,完成整個MVC請求流程。
