前言
SpringMVC 可以說是我們日常開發中最依賴的 Spring 組件了,它基於 Servlet
容器實現,允許我們通過註解的方式開發 Web 程序。在本篇文章,將深入 SpringMVC 源碼,梳理 SpringMVC 對 Web 請求處理流程,弄懂相關核心組件的原理,最終做到在使用的時候知其然也知其所以然。
一、接受並分發Web請求
SpringMVC 的核心類是 DispatcherServlet
,它是 spring 實現的 Servlet
容器,當我們的 web 請求到達 DispatcherServlet
後,它將會根據各種規則將將請求分發到指定的處理器中,比如被 @RequestMapping
註解的方法。
和 spring 的其他核心組件一樣,DispatcherServlet
也有比較複雜的層級結構,不過我們暫時不關心,僅從 Servlet
提供的 doGet
方法開始,跟蹤 spring 完成一次普通請求的全流程。
1、processRequest
當我們調用 Servlet
的 doGet
/ doPost
以及其他類型的方法時,請求都會被統一的轉發到 processRequest
方法:
// FrameworkServlet
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
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());
// 將區域和請求參數設置噹噹前線程的上下文中
initContextHolders(request, localeContext, requestAttributes);
try {
// 完成請求
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new ServletException("Request processing failed: " + ex, ex);
}
finally {
// 重置請求上下文
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
// 發佈 ServletRequestHandledEvent 事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
processRequest
方法基本定義了完成請求的基本步驟:
- 初始化區域上下文
LocaleContext
,用於後續國際化相關的功能; - 初始化請求參數對象
ServletRequestAttributes
; - 初始化異步請求管理器
WebAsyncManager
; - 調用
resetContextHolders
將區域上下文與請求參數對象綁定到當前線程上下文中; - 調用
doService
真正的處理本次請求; - 調用
resetContextHolders
重置當前請求上下文; - 調用
publishRequestHandledEvent
發佈請求響應事件ServletRequestHandledEvent
;
2、doService
doService
在 processRequest
的基礎上進一步的爲本次請求做了一些準備:
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.
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.
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);
}
// 獲取請求路徑,並且將其放入請求參數中
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) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
這些準備包括:
- 爲
include_request
創建請求參數快照(include request 一般是指在一個請求中包含另一個請求的數據,比如通過 JSP 中的 include 標籤實現的菜單欄); - 初始化
FlashMap
,該集合用於在重定向時在兩個請求之間共享參數; - 解析並緩存本次請求路徑;
- 在請求參數對象中設置一些當上下文參數和配置,比如當前
Servlet
容器所在的WebApplicationContext
,要使用的主題解析器ThemeResolver
或地域解析器LocaleResolver
等等; - 調用
doDispatch
方法,將請求分發給具體的請求處理器完成處理; - 移除請求參數快照,和請求路徑緩存;
3、doDispatch
doDispatch
方法是真正使用對應的請求處理器完成請求的地方,它是 DispatcherServlet
最核心的方法,我們所知道的各種攔截器、處理器、視圖解析以及異常處理邏輯都在這一步完成:
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 類型(表單/文件上傳)的請求?
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 獲取請求處理器鏈
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 將請求處理器鏈包裝爲處理器適配器
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 處理 get 和 head 請求的緩存機制
// 即如果是 get 或 head 請求, 就檢查客戶端發送的請求頭中的If-Modified-Since值,如果指定的值可以與最後修改時間 lastModified 匹配,即資源沒有被修改,則返回304 Not Modified響應。
// Process last-modified header, if supported by the handler.
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;
}
}
// 執行攔截器的 preHandle 方法,若有攔截器返回了 false 則立即完成本次請求
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 處理請求
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果本次請求是異步請求,且以及開始,那麼立刻完成本次請求
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 執行攔截器的 postHandle 放阿飛
applyDefaultViewName(processedRequest, mv);
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.
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
// 執行攔截器的 afterCompletion 方法
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new ServletException("Handler processing failed: " + err, 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.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
如果暫且不考慮異步處理相關的邏輯,那麼這段代碼主要完成了以下任務:
- 對請求進行預處理,包括檢查是否爲 multipart 類型的請求(表單/文件上傳);
- 獲取處理器執行鏈
HandlerExecutionChain
幷包裝爲處理器適配器HandlerAdapter
; - 處理 GET 和 HEAD 請求的緩存機制,根據上一個
HandlerAdapter
的最後修改時間檢查是否需要返回304 Not Modified響應; - 執行攔截器
HandlerInterceptor
的preHandle
方法,如果有任何攔截器返回 false,則立即完成請求; - 調用處理器適配器的
handle
方法執行業務邏輯,並返回ModelAndView
對象。 - 執行攔截器
HandlerInterceptor
的postHandle
方法; - 處理請求結果,包括渲染視圖、處理異常等。
- 在處理過程中,如果發生異常,執行攔截器的
afterCompletion
方法; - 清理處理multipart請求的資源。
至此,我們已經大概瞭解的 springMVC 處理請求的核心過程,不過要了解更詳細的內容,就需要深入具體的組件中了。
二、獲取請求處理器
處理器執行鏈 HandlerExecutionChain
是實際用於將請求打到業務邏輯的組件,在 DispatcherServlet
中需要先獲取處理器映射 HandlerMapping
然後再調用它的 getHandler
方法獲取:
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
在 DispatcherServlet
容器刷新時,默認會通過 initHandlerMappings
從 spring 容器中獲得全部的 HandlerMapping
實例。
我們常用的 @Controller
加 @RequestMapping
的註解式配置,是通過 RequestMappingHandlerMapping
實現的。接下來我們以它爲例,瞭解請求處理器是的運行機制。RequestMappingHandlerMapping
有着比較複雜的層級結構,我們重點關注 AbstractHandlerMapping
、AbstractHandlerMethodMapping
以及 RequestMappingInfoHandlerMapping
四個層次的抽象。
1、解析處理器方法
在開始之前,我們得先知道 @Controller
中帶有 @RequestMapping
的方法到底是怎麼變成可以用於處理器請求的 “處理器” 的。
首先,RequestMappingInfoHandlerMapping
是 AbstractHandlerMethodMapping
的子類,它規定了 AbstractHandlerMethodMapping
的泛型對象爲 RequestMappingInfo
,實際上 RequestMappingInfo
就對應了方法上的 @RequestMapping
註解。
而談到它是如何被從 Controller
中解析,並加載到 MappingRegistry
中的,我們就需要回到 AbstractHandlerMethodMapping
中,該抽象類實現了 InitializingBean
回調接口,在屬性初始化完畢後,它將會觸發 initHandlerMethods
方法:
/**
* Detects handler methods at initialization.
* @see #initHandlerMethods
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
// 1.獲取可用於解析的 bean 的 beanName
for (String beanName : getCandidateBeanNames()) {
// SCOPED_TARGET_NAME_PREFIX = "scopedTarget."
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 如果 beanName 不以 scopedTarget 開頭,則
processCandidateBean(beanName);
}
}
// 完成解析後的回調
handlerMethodsInitialized(getHandlerMethods());
}
/**
* Determine the names of candidate beans in the application context.
* @since 5.1
* @see #setDetectHandlerMethodsInAncestorContexts
* @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors
*/
protected String[] getCandidateBeanNames() {
// 簡而言之,掃描容器及父容器中的所有的 bean
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
// 解析並加載處理器方法
detectHandlerMethods(beanName);
}
}
// 用於後續擴展的鉤子方法,不過此處只是輸出了一下日誌
protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {
// Total includes detected mappings + explicit registrations via registerMapping
int total = handlerMethods.size();
if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) {
logger.debug(total + " mappings in " + formatMappingName());
}
}
// RequestMappingHandlerMapping 實現方法,即判斷類上是否有 @Controller 註解
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}
在 initHandlerMethods
方法,共完成了下述步驟:
- 通過
getCandidateBeanNames
獲取 spring 容器(當前及父容器)中所有的 beanName; - 如果
beanName
對應的 bean :- 如果不以 “scopedTarget ” 開頭,即不是一個作用域代理對象(換而言之就是全局有效的);
- 並且
isHandler
方法確認該 bean 是一個處理器對象(即類上帶有@Controller
註解); - 那麼就通過
detectHandlerMethods
解析該 bean 中帶有@RequestMapping
註解的方法,變爲RequestMappingInfo
與對應的HandlerMethod
,並註冊到MappingRegistery
中;
- 完成上述操作後,調用
handlerMethodsInitialized
方法輸出 debug 日誌;
2、註冊處理器方法
接下來我們繼續進入 detectHandlerMethods
方法,在這個方法中,真正的完成了掃描 Controller
並解析註解方法的邏輯:
/**
* Look for handler methods in the specified handler bean.
* @param handler either a bean name or an actual handler instance
* @see #getMappingForMethod
*/
protected void detectHandlerMethods(Object handler) {
// 如果 handler 是字符串,則以其作爲 beanName 從容器中獲取 beanType,否則直接獲取 beanType
Class<?> handlerType = (handler instanceof String beanName ?
obtainApplicationContext().getType(beanName) : handler.getClass());
if (handlerType != null) {
// 爲了避免錯誤的拿到 CGLib 代理類,此次需要獲取 bean 的真正類型
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 從方法上獲取 RequestMappingInfo 對象
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
else if (mappingsLogger.isDebugEnabled()) {
mappingsLogger.debug(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
// 將處理器方法和對應的元數據註冊到 MappingRegister 中
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
// ====================== 獲取 RequestMappingInfo ======================
// RequestMappingHandlerMapping 實現方法,即判斷類上是否有 @RequestMapping 註解
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
// 創建 RequestMappingInfo 對象
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
// 這裏會解析包括 method、params 等等註解屬性
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}
// ====================== 註冊 RequestMappingInfo 和 HandlerMethod ======================
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
// AbstractHandlerMethodMaping.MappingRegistry
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
// 創建一個處理器方法
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 校驗一下,避免重複註冊
validateMethodMapping(handlerMethod, mapping);
// 從 RequestMappingInfo 中獲取請求路徑(@RequestMapping 可以綁定多個路徑),並添加到路徑索引表
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
// 通過命名策略聲明請求名稱,並添加到名稱索引表
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
// 獲取跨域配置,並添加到跨域索引射表
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
// 爲 RequestMappingInfo 註冊一個 MappingRegistration,裏面有包括路徑、名稱、跨域配置在內的所有相關信息
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
// 創建一個處理器方法,注意此處僅放入了 beanName (因爲 bean 可能還沒有初始化完成)
if (handler instanceof String beanName) {
return new HandlerMethod(beanName,
obtainApplicationContext().getAutowireCapableBeanFactory(),
obtainApplicationContext(),
method);
}
return new HandlerMethod(handler, method);
}
在 detectHandlerMethods
這一步做了非常多的事情,但是根據內容大概可以分爲兩部分:
- 掃描被
@RequestMapping
註解的方法:- 爲了避免錯誤的拿到 CGLib 代理類,獲取 bean 的真正類型;
- 掃描被
@RequestMapping
註解的方法,並將通過getMappingForMethod
方法爲註解創建RequestMappingInfo
;
- 通過
registerHandlerMethod
方法將RequestMappingInfo
與對應的註解方法註冊到MappingRegistery
:- 通過
createHandlerMethod
方法將註解方法適配爲處理器方法HandlerMethod
,並且考慮到此時 bean 可能尚未初始化,因此只記錄了 beanName,而沒有直接獲取該 bean; - 檢查是否重複註冊了
HandlerMethod
; - 根據
RequestMappingInfo
中的屬性建立快速索引表:- 根據
path
建立請求路徑索引; - 根據
name
建立請求名稱索引; - 根據
CorsConfiguration
建立跨域索引;
- 根據
- 創建一個包含請求元數據
RequestMappingInfo
、處理器方法HandlerMethod
、請求路徑、名稱、跨域配置所有信息在內的註冊信息對象MappingRegistration
,並建立RequestMappingInfo
與其的映射關係;
- 通過
至此,我們掃描了所有的 **Controller**
,並掃描了其中帶有 **@RequestMapping**
註解的方法,並最終將其變爲 **RequestMappingInfo**
與對應 **HandlerMethod**
,並保存到了 **AbstractHandlerMethodMapping**
中的 **MappingRegistry**
裏。
3、創建處理器執行鏈
先從 AbstractHandlerMapping
開始,它實現了 HandlerMapping
的 getHandler
方法:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 獲取用於處理請求的內部處理器,此處的 handler 可能是多種類型的對象
Object handler = getHandlerInternal(request);
// 如果沒有對應的處理器,則嘗試獲取一個默認的處理器
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// 如果 handler 是字符串,則將其作爲 beanName 從 spring 容器查找對應的處理器
// Bean name or resolved handler?
if (handler instanceof String handlerName) {
handler = obtainApplicationContext().getBean(handlerName);
}
// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.hasCachedPath(request)) {
initLookupPath(request);
}
// 將處理器適配爲處理器鏈
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
logger.debug("Mapped to " + executionChain.getHandler());
}
// 處理跨域問題
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
// 將內部處理器適配爲處理器執行鏈
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain handlerExecutionChain ?
handlerExecutionChain : new HandlerExecutionChain(handler));
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor mappedInterceptor) {
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
getHandler
中主要做了一下幾件事:
- 獲取請求處理器:
- 先嚐試通過
getHandlerInternal
獲取內部請求處理器; - 如果沒有則嘗試通過
getDefaultHandler
獲取默認的請求處理器; - 再沒有則直接返回
null
,說明此HandlerMapping
無法處理當前的請求; - 如果請求處理器不爲
null
,則它可能是多種類型的對象,其中,若其爲字符串,則將其作爲 beanName 從 spring 容器查找對應的處理器;
- 先嚐試通過
- 將請求處理器通過
getHandlerExecutionChain
方法適配爲處理器執行鏈HandlerExecutionChain
:- 若請求處理器不是一個
HandlerExecutionChain
,則新建一個HandlerExecutionChain
包裝它; - 將當前
HandlerMapping
中的可以應用與當前請求的HandlerInterceptor
全部添加到處理器執行鏈中,若是MappedInterceptor
則將其內部的包裝的Interceptor
添加到執行鏈;
- 若請求處理器不是一個
- 若當前有跨域配置,則進行跨域處理;
4、查找可以的處理器方法
接着我們進入 AbstractHandlerMethodMapping
中的 getHandlerInternal
,它進一步揭露的方法是如何適配爲請求處理器的:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 獲取要匹配的請求路徑,一般即我們所在 @RequestMapping 中配置的 path
String lookupPath = initLookupPath(request);
// 加讀鎖
this.mappingRegistry.acquireReadLock();
try {
// 根據請求路徑找到與其匹配的方法
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
protected String initLookupPath(HttpServletRequest request) {
if (usesPathPatterns()) {
request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
String lookupPath = requestPath.pathWithinApplication().value();
return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
}
else {
return getUrlPathHelper().resolveAndCacheLookupPath(request);
}
}
我們重點關注 lookupHandlerMethod
方法:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 從 MappingRegistry 直接根據請求路徑查找處理器方法,將其添加到候選列表
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果直接根據路徑找不到匹配的方法,直接將 MappingRegistry 中的所有處理器方法添加到候選列表
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
// 默認首個爲最匹配的,若有不止一個匹配的處理器方法,則根據 MatchComparator 從中找到最匹配的那個處理器方法
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
for (Match match : matches) {
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
}
else {
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);
// 獲取處理器方法 HandlerMethod
return bestMatch.getHandlerMethod();
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
// getMatchingMapping 是個抽象方法,用於根據條件獲取一個與其匹配的泛型對象
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));
}
}
}
在 AbstractHandlerMethodMapping
中, getHandlerInternal
這一步主要做了以下兩件事:
- 解析並獲取請求路徑;
- 根據請求路徑查找對應的處理器方法
HandlerMethod
,這個過程中可能找到多個與請求參數匹配的處理器方法,但是我們藉助MatchComparator
只取最匹配的那個;
另外,值得注意的是,在這裏出現兩個新的東西:
- 泛型對象 T,它是與處理器放方法
HandlerMethod
對應的元數據,用於根據 T 找到對應的處理器方法。
比如@RequestMapping
對應的元數據就是 T,而被註解的方法對應的就是HandlerMethod
,我們會在下文的RequestMappingInfoHandlerMapping
更進一步瞭解這兩者; - 處理器註冊表
MappingRegistry
,它是AbstractHandlerMethodMapping
中的一個內部類,用於維護泛型對象 T 與HandlerMethod
之間的映射關係。
在下文的RequestMappingInfoHandlerMapping
中我們會看到HandlerMethod
是在什麼時候註冊進去的;
至此,我們知道基於方法的 **HandlerMapping**
,**getHandlerInternal**
** 方法將會返回一個 **HandlerMethod**
,後續的 **HandlerExecutionChain**
將基於它創建**。
三、通過適配器執行請求
在上文,我們知道了 spring 是如何根據 HandlerMapping
獲得處理器執行鏈 HandlerExecutionChain
的,不過實際上在 doDispatch
方法的執行過程中,它內部包裝的 Handler
將被適配爲一個處理器適配器 HandlerAdapter
去執行各種操作。
與 HandlerMapping
一樣,處理器適配器同樣是在 DispatcherServlet
容器刷新時,通過 initHandlerAdapters
從 spring 容器中加載的。
它具備一個 supports
方法,用於根據通過 HandlerMapping
獲得的 HandlerExecutionChain
裏頭包裝的 Handler
對象判斷是否可以應用該適配器。由於在上文我們以 RequestMappingHandlerMapping
爲例講了 HandlerMapping
,因此下文我們同樣以適配 HandlerMethod
的處理器適配器 RequestMappingHandlerAdapter
爲例,介紹處理器適配是如何基於處理器完成實際請求的。
1、處理器適配器
當我們調用 RequestMappingHandlerAdapter
的 handle
方法時:
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
// AbstractHandlerMethodAdapter.handle
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
// RequestMappingHandlerAdapter.handleInternal
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
// 1、如果有 session,則同一時間內僅有一個線程可以處理器改 session 對應的請求
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 {
// 沒有 session 或者不要求對 session 的請求必須同步,那就直接執行
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
// 如果請求頭中有 Cache-Control,則緩存當前請求的響應
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
這個方法邏輯比較簡單,其實就是執行 HandlerMethod
,然後獲得 ModelAndView
對象。
2、執行請求
接下來我們進入 invokeHandlerMethod
方法:
/**
* Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
* if view resolution is required.
* @since 4.2
* @see #createInvocableHandlerMethod(HandlerMethod)
*/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 使用 ServletInvocableHandlerMethod 包裝 HandlerMethod,並且:
// 1.設置參數解析器;
// 2.設置返回值處理器;
// 3.設置數據綁定工廠;
// 4.設置方法參數名發現器;
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 創建並初始化視圖容器
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 創建 AsyncWebRequest 用於執行異步請求
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
// 獲取異步請求管理器,並設置並設置執行任務的線程池、異步 Web 請求和異步請求攔截器。
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
// 如果存在併發結果,則獲取結果並將其從管理器中清空,
// 然後將結果也封裝到 ServletInvocableHandlerMethod 中
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 執行 HandlerMethod,處理請求
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// 如果異步請求已啓動(當前是個異步請求),則直接返回 null
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 如果請求正常結束了,獲取對應的視圖對象
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
// 清空 ServletWebRequest 中的一些緩存
webRequest.requestCompleted();
}
}
invokeHandlerMethod
方法主要關注三個方面的內容:
- 設置了請求方法的參數解析器,數據綁定工廠等等,這一步爲我們熟悉的
@RequestParams
、@PathVariable
或者@RquestBody
註解提供了支持; - 設置了請求的返回值處理器,這一步爲
@ResponseBody
註解,與傳統 MVC 中的的各種View
提供了支持; - 處理了異步請求相關的邏輯,這爲 SpringMVC 支持一步 Servlet 提供了支持;
異步的內容我們暫且先忽略,那麼此處主要需要了解的只有參數解析器和返回值解析器了。那麼,讓我們進入 ServletInvocableHandlerMethod
的 invokeAndHandle
方法:
/**
* Invoke the method and handle the return value through one of the
* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type (not resolved)
*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 調用方法,獲得返回值,在這個過程中參數解析器和返回值解析器將會生效
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 使用返回值解析器處理器返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
3、方法參數解析器
當調用 invokeForRequest
方法時,將會真正的調用方法,不過在調用前,會通過方法參數解析器 HandlerMethodArgumentResolver
從請求中解析出方法調用所需要的參數:
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 解析獲得方法的調用參數
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 調用方法,執行業務邏輯
return doInvoke(args);
}
/**
* Get the method argument values for the current request, checking the provided
* argument values and falling back to the configured argument resolvers.
* <p>The resulting array will be passed into {@link #doInvoke}.
* @since 5.1.2
*/
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 獲取方法參數
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 確認當前的參數解析器是否支持處理器改類型的參數
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 使用參數解析器獲取方法的實際調用參數
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
@Nullable
protected static Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) {
if (!ObjectUtils.isEmpty(providedArgs)) {
for (Object providedArg : providedArgs) {
if (parameter.getParameterType().isInstance(providedArg)) {
return providedArg;
}
}
}
return null;
}
此處的邏輯沒有太多複雜的邏輯,就是簡單的將方法參數依次取出,然後使用參數解析器 HandlerMethodArgumentResolver
去解析得到實際用於調用的參數。比如:
RequestParamMethodArgumentResolver
可以根據@RequestParam
註解的名稱屬性或註解參數的名稱,從請求參數中獲取並賦值給對應的方法參數;PathVariableMethodArgumentResolver
可以從請求路徑中提取佔位符對應的參數值,然後將其賦給與佔位符同名的帶有@PathVariable
註解的方法參數;RequestResponseBodyMethodProcessor
可以從請求的 body 中讀取 JSON 對象,並反序列化後賦值給帶有@RequestBody
註解的方法參數(注意,它同時也是@ResponseBody
註解的處理器);
4、方法返回值處理器
在 RequestMappingHandlerAdapter
的 invokeAndHandle
方法中的這段代碼中,spring 將會使用返回值處理器 HandlerMethodReturnValueHandler
對其進行解析:
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 使用返回值解析器處理器返回值
// 完成請求後,返回的視圖即爲 ModelAndViewContainer 中的視圖
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
與方法參數解析器 HandlerMethodArgumentResolver
不同,方法返回值處理器 HandlerMethodReturnValueHandler
的主要目的是處理請求後的 View
展示問題,這也是它爲何叫處理器而不是解析器的原因吧。
舉個例子,在前後端不分離的時代,Controller
中方法的返回值通常會是一個相對路徑,它用於表示 classpath
下的 template
文件夾中用於渲染頁面的某個模板文件,後端在請求完成後會使用模板引擎將模板文件渲染爲 HTML 頁面,然後將其返回給發起請求的客戶端(通常就是瀏覽器)。而在前後端分離的時代,我們通常會在方法上添加一個 @ResponseBody
註解,使得後端在完成請求後,返回一個 JSON 字符串。
實際上兩者的區別在於選擇了不同的返回值處理器。我們以處理 @ResponseBody
註解的 RequestResponseBodyMethodProcessor
爲例:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 注意,該處理器實際上並沒有向容器中設置任何 view
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
if (returnValue instanceof ProblemDetail detail) {
outputMessage.setStatusCode(HttpStatusCode.valueOf(detail.getStatus()));
if (detail.getInstance() == null) {
URI path = URI.create(inputMessage.getServletRequest().getRequestURI());
detail.setInstance(path);
}
}
// 使用 MessageConverter 序列化返回值並寫入 respone 中
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
在 RequestResponseBodyMethodProcessor
的 handleReturnValue
方法中:
- **沒有向
**ModelAndViewContainer**
中設置任何 ****View**
; - 直接通過
HttpMessageConverter
將返回值序列化後寫入了response
中;
而傳統的 ModelAndViewMethodReturnValueHandler
則有着與其截然不同的實現:
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
ModelAndView mav = (ModelAndView) returnValue;
if (mav.isReference()) {
String viewName = mav.getViewName();
mavContainer.setViewName(viewName);
if (viewName != null && isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else {
// 獲取要渲染的頁面,並設置到容器
View view = mav.getView();
mavContainer.setView(view);
if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
mavContainer.setRedirectModelScenario(true);
}
}
mavContainer.setStatus(mav.getStatus());·
// 將 Model 中的參數設置到容器中
mavContainer.addAllAttributes(mav.getModel());
}
我們可以非常直觀的注意到,ModelAndViewMethodReturnValueHandler
**直接創建了一個 ****View**
,並將其設置到 ModelAndViewContainer
中。
值得一提的是,
RequestResponseBodyMethodProcessor
同時用於支持@RequestBody
與@ResponseBody
,由於讀寫 body 這一行爲的特殊性,spring 非常貼心的提供了兩個默認支持的 AOP 擴展接口RequestBodyAdvice
與ResponseBodyAdvice
,它們支持在讀取/寫入 body 之前進行一些自定義的操作。
四、異常處理
1、執行流程
現在讓我們回到 DispatcherServlet
的 doDispatch
方法,在這個方法中,當我們完成 HandlerExecutionChain
與 HandlerAdapter
的調用後,就需要針對執行結果進行處理了:
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 調用 HandlerExecutionChain 與 HandlerAdapter
}
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 ServletException("Handler dispatch failed: " + err, err);
}
// 對執行結果或拋出的異常進行處理
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
我們進入 processDispatchResult
:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable 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);
}
}
// 若有必要,渲染視圖
// Did the handler return a view to render?
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()) {
// Concurrent handling started during a forward
return;
}
// 觸發 HandlerInterceptor 的 afterCompletion 方法
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
在 processDispatchResult
方法中,主要乾了三件事:
- 如果有運行時異常,則對捕獲的異常通過
processHandlerException
進行處理,並將正常請求返回的ModelAndView
替換爲錯誤頁面; - 如果
ModelAndView
不爲空,那麼就通過render
方法渲染視圖; - 如果當前請求是一個已經開始的異步請求,那麼就不觸發
HandlerInterceptor
的afterCompletion
回調方法;
考慮到現在的應用大多都是前後端分離的,因此在這裏暫且忽略關於視圖渲染相關的邏輯,而僅關注異常處理的 processHandlerException
方法:
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.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.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
這裏的 HandlerExceptionResolver
就是我們一般說的異常攔截器,當然,我們更熟悉的可能是基於 @ControllerAdvice
和 @ExceptionHandler
實現的全局異常攔截器,不過實際上它也是基於 HandlerExceptionResolver
實現的。
2、全局異常攔截器
嚴格來說,spring 中並沒有名爲全局異常攔截器的 HandlerExceptionResolver
,我們一般用於實現該效果的組件是 ExceptionHandlerExceptionResolver
,藉助它,我們可以在一個類上添加 @ControllerAdvice
將其聲明爲控制器切面,然後再在其中的方法上通過 @ExceptionHandler
註解聲明該方法用於處理指定類型的異常(實際上不加註解,而是直接讓方法參數的類型是異常也可以)。ExceptionHandlerExceptionResolver
實現了 InitializingBean
回調接口:
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
// 加載所有被 @ControllerAdvice 註解的 bean 中的所有帶有 @ExceptionHandler 註解的方法
initExceptionHandlerAdviceCache();
// 加載默認的參數解析器
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
// 加載默認的返回值解析器
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
// 通過 ControllerAdviceBean 獲取容器中所有帶有 @ControllerAdvice 註解的 bean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 創建 ExceptionHandlerMethodResolver,它會掃描類中所有帶有 @ExceptionHandler 註解的方法
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
if (logger.isDebugEnabled()) {
int handlerSize = this.exceptionHandlerAdviceCache.size();
int adviceSize = this.responseBodyAdvice.size();
if (handlerSize == 0 && adviceSize == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " +
handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
}
}
}
/**
* Return the list of argument resolvers to use including built-in resolvers
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
*/
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
return resolvers;
}
/**
* Return the list of return value handlers to use including built-in and
* custom handlers provided via {@link #setReturnValueHandlers}.
*/
protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
// Single-purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
// Annotation-based return value types
handlers.add(new ServletModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
// Multi-purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
// Catch-all
handlers.add(new ServletModelAttributeMethodProcessor(true));
return handlers;
}
在這個方法中,spring 做了這些事情:
- 加載容器中所有帶有
@ControllerAdvice
註解的 bean,然後爲其創建異常處理器方法解析器ExceptionHandlerMethodResolver
,若裏面能解析到至少一個異常處理器方法,那麼就將它緩存到exceptionHandlerAdviceCache
中; - 加載必要的參數解析器
HandlerMethodArgumentResolver
與返回值處理器HandlerMethodReturnValueHandler
,其中很大一部分與RequestMappingHandlerMapping
用到的一樣,這也是爲什麼我們可以在被@ExceptionHandler
註解的方法上面加@ResponseBody
註解還能生效的原因;
3、異常處理器方法解析器
當我們在 initExceptionHandlerAdviceCache
的時候,會爲每一個被 @ControllerAdvice
註解的 bean 創建一個異常攔截器方法解析器 ExceptionHandlerMethodResolver
:
public class ExceptionHandlerMethodResolver {
/**
* A filter for selecting {@code @ExceptionHandler} methods.
*/
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
// 攔截的異常與處理異常的方法的對應關係
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
// 具體的異常類與對應的處理方法的緩存,用來避免每次處理異常的時候都要遍歷 mappedMethods 尋找匹配的異常類
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
/**
* A constructor that finds {@link ExceptionHandler} methods in the given type.
* @param handlerType the type to introspect
*/
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
/**
* Extract exception mappings from the {@code @ExceptionHandler} annotation first,
* and then as a fallback from the method signature itself.
*/
@SuppressWarnings("unchecked")
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<>();
detectAnnotationExceptionMappings(method, result);
// 如果方法上沒有 @ExceptionHandler 註解,那麼就根據參數中是否有異常類型的參數確認方法支持處理的異常類型
if (result.isEmpty()) {
for (Class<?> paramType : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
}
}
}
if (result.isEmpty()) {
throw new IllegalStateException("No exception types mapped to " + method);
}
return result;
}
private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
// 根據方法上的 @ExceptionHandler 註解確認方法支持處理哪幾類異常
ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
Assert.state(ann != null, "No ExceptionHandler annotation");
result.addAll(Arrays.asList(ann.value()));
}
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
// 每種類型的異常只能有一個方法與其對應
Method oldMethod = this.mappedMethods.put(exceptionType, method);
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
}
}
在 ExceptionHandlerMethodResolver
創建的時候,會遍歷類中的每一個方法:
- 若方法有
@ExceptionHandler
註解,那麼就根據註解確認方法支持處理哪幾種類型的異常,否則就根據方法參數是否有Throwable
類型的參數推測支持處理哪幾種異常; - 在
mappedMethods
中記錄異常類型與方法的對應關係,並且每種類型的異常只能有一個方法與其對應;
4、異常處理器方法的調用
當我們在 DispatcherServlet
的 processHandlerException
方法調用異常解析器時,將會遍歷所有的 ExceptionHandlerMethodResolver
,若遍歷到 ExceptionHandlerExceptionResolver
則會調用其 resolveException
方法:
// AbstractHandlerExceptionResolver
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
// 是否支持處理該異常
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug(buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
// =================== 是否支持處理該異常 & 處理器 ===================
// AbstractHandlerExceptionResolver
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (Class<?> handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
return !hasHandlerMappings();
}
// AbstractHandlerMethodExceptionResolver,重寫自 AbstractHandlerExceptionResolver
@Override
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
if (handler == null) {
return super.shouldApplyTo(request, null);
}
// 如果 handler 是 HandlerMethod,使用 @RequestMapping 聲明的處理器就是 HandlerMethod 的一種
else if (handler instanceof HandlerMethod handlerMethod) {
handler = handlerMethod.getBean();
return super.shouldApplyTo(request, handler);
}
else if (hasGlobalExceptionHandlers() && hasHandlerMappings()) {
return super.shouldApplyTo(request, handler);
}
else {
return false;
}
}
// =================== 處理異常 ===================
// AbstractHandlerExceptionResolver
@Override
@Nullable
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
return doResolveHandlerMethodException(request, response, handlerMethod, ex);
}
// ExceptionHandlerExceptionResolver
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// 創建一個請求對象,注意,此處的處理流程與 RequestMappingHandlerAdapter 的 invokeHandlerMethod 高度一致
// 因此從某種程度上來說,我們可以認爲它相當於重走了一遍 @RequestMapping 方法
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
// 設置參數解析器器與返回值處理器
if (this.argumentResolvers != null) {
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
ArrayList<Throwable> exceptions = new ArrayList<>();
try {
if (logger.isDebugEnabled()) {
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
}
// Expose causes as provided arguments as well
Throwable exToExpose = exception;
while (exToExpose != null) {
exceptions.add(exToExpose);
Throwable cause = exToExpose.getCause();
exToExpose = (cause != exToExpose ? cause : null);
}
Object[] arguments = new Object[exceptions.size() + 1];
exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
arguments[arguments.length - 1] = handlerMethod;
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
}
catch (Throwable invocationEx) {
// Any other than the original exception (or a cause) is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
}
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelMap model = mavContainer.getModel();
HttpStatusCode status = mavContainer.getStatus();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}
@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
// Local exception handler methods on the controller class itself.
// To be invoked through the proxy, even in case of an interface-based proxy.
// 檢查 Controller 裏面是不是已經有異常處理器方法,如果有就先用本地的
handlerType = handlerMethod.getBeanType();
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.computeIfAbsent(
handlerType, ExceptionHandlerMethodResolver::new);
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method, this.applicationContext);
}
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
// 如果該方法所在的 Controller 中沒有異常處理器方法,那麼再用 @ControllerAdvice 中的異常處理器方法
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext);
}
}
}
return null;
}
// =================== 尋找支持處理異常的方法 ===================
// ExceptionHandlerMethodResolver
@Nullable
public Method resolveMethod(Exception exception) {
return resolveMethodByThrowable(exception);
}
// ExceptionHandlerMethodResolver
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
// 根據異常類型尋找支持處理的方法
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
// 如果找不到,就遞歸獲取異常堆棧的下一層異常進行匹配
Throwable cause = exception.getCause();
if (cause != null) {
method = resolveMethodByThrowable(cause);
}
}
return method;
}
// ExceptionHandlerMethodResolver
@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
// 先從緩存裏面找該類型對應的處理器方法,沒有再遍歷處理器方法進行匹配
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
this.exceptionLookupCache.put(exceptionType, method);
}
return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
}
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
// 遍歷處理器方法進行匹配
List<Class<? extends Throwable>> matches = new ArrayList<>();
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
// 異常類型可以是要處理的異常類型的父類或者父接口
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
// 如果找到了多個支持的異常處理器方法,那麼根據他們支持的異常類在繼承樹中的深度進行匹配
// 比如,一個處理器支持處理 Throwable,而另一個支持處理 RuntimeException,那麼將優先選擇後者
if (!matches.isEmpty()) {
if (matches.size() > 1) {
matches.sort(new ExceptionDepthComparator(exceptionType));
}
return this.mappedMethods.get(matches.get(0));
}
else {
return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
}
}
這一段代碼非常長,但是整體邏輯並不複雜,總共就按順序幹了三件事:
- 根據
shouldApplyTo
判斷是否支持處理該異常; - 通過
getExceptionHandlerMethod
方法獲取用於處理該類型異常的處理器方法:- 先爲請求方法所在的
Controller
創建ExceptionHandlerMethodResolver
,看看裏面有沒有支持處理該異常的方法,如果有就優先使用本地的異常處理方法; - 遍歷緩存的已有
ExceptionHandlerMethodResolver
,一次調用它們的resolveMethod
方法;- 調用
resolveMethodByThrowable
方法,在裏面嘗試通過resolveMethodByExceptionType
方法找到支持處理該異常的方法,若沒有則遞歸異常堆棧中的所有異常直到找到爲止; - 在
resolveMethodByExceptionType
中先根據異常類型尋找緩存的處理器方法,如果沒有就調用getMappedMethod
方法再次尋找並緩存對應的異常處理器方法; - 在
getMappedMethod
中遍歷所有的支持處理該異常的處理器方法:- 若只找到一個,那麼就直接返回該方法;
- 若找到多個,則比較它們支持處理的異常類型的繼承樹中的深度,並返回深度最大的那個異常。
舉個例子,若現在又兩個處理器方法,一個支持 Throwable,而另一個支持 RuntimeException,那麼將優先選擇後者;
- 調用
- 先爲請求方法所在的
- 在
doResolveHandlerMethodException
方法中完成對錯誤請求的處理,這個步驟大部分與RequestMappingHandlerAdapter
的invokeHandlerMethod
方法一致:- 完成步驟二,爲異常找到對應的異常處理器方法,獲得與該請求對應的
ServletInvocableHandlerMethod
對象; - 設置必要的方法參數解析器與方法返回值處理器;
- 調用
ServletInvocableHandlerMethod
,完成對異常的處理,並獲得ModelAndViewContainer
;
- 完成步驟二,爲異常找到對應的異常處理器方法,獲得與該請求對應的
最終,異常解析器返回的 ModelAndViewContainer
將會替代正常的 HandlerAdapter
返回的 mv 容器。
總結
1、接受並分發 Web 請求
DispatcherServlet
是 Spring MVC 的核心類,它實現了Servlet
接口。其父類爲FrameworkServlet
,它在內部實現了Servlet
中定義的doXXX
方法(比如doGet
和doPost
);- 當
FrameworkServlet
接收到請求時,會將對Servlet
的請求被轉發到內部的processRequest
方法,在這個方法中,它做了下述三件事:- 將會初始化一些諸如國際化、請求參數、異步管理器之類的上下文資源;
- 調用
doService
方法完成請求; - 在請求後釋放資源,並且發佈
ServletRequestHandledEvent
事件;
- 其中,
doService
方法又進一步對請求做了一些處理,包括緩存請求參數、生成重定向時使用的參數閃存等,並最終調用doDispatch
方法真正的執行請求; DispatcherServlet
實現了doDispatch
抽象方法,在這一步將真正的完成請求:- 找到可應用於該請求的
HandlerMapping
獲取處理器執行器HandlerExecutionChain
,它包含必要的攔截器HandlerInterceptor
與執行業務請求的處理器Handler
; - 根據獲取到的處理器
Handler
,找到幷包裝爲對應的處理器適配器HandlerAdapter
,裏面包含了參數解析、返回值解析等邏輯; - 執行所有攔截器
HandlerInterceptor
的preHandle
方法; - 執行處理器適配器
HandlerAdapter
的handle
方法,並真正的觸發處理器Handler
中的邏輯; - 執行所有攔截器
HandlerInterceptor
的postHandle
方法; - 對執行結果進行解析,如果存在異常則使用執行器異常解析器
HandlerExceptionResolver
對異常進行處理; - 執行所有攔截器
HandlerInterceptor
的afterCompletion
方法;
- 找到可應用於該請求的
2、獲取請求的處理器
HandlerMapping
是 MVC 中用於獲取處理器執行鏈HandlerExecutionChain
的核心組件,我們平時基於@Controller
+@RequestMapping
的註解式開發依賴其實現類RequestMappingHandlerMapping
;- 當
RequestMappingHandlerMapping
完成屬性初始化時,將會觸發afterPropertiesSet
回調,此時它將會掃描容器中所有帶有@Controller
註解的 bean,並解析其中帶有@RequestMapping
註解的方法,然後將註解的信息解析爲RequestMappingInfo
作爲 key,方法封裝爲HandlerMethod
(它就是後面要用的處理器Handler
的一種)作爲 value,並註冊到內部的映射註冊表MappingRegistery
中; - 當通過
RequestMappingHandlerMapping
的getHandler
方法獲取處理器鏈時,將會遍歷MappingRegistry
中的 key —— 也就是RequestMappingInfo
,並根據請求類型、 URL 等參數獲取一個最匹配的HandlerMethod
,並將RequestMappingHandlerMapping
中的攔截器HandlerInterceptor
**與處理器HandlerMethod
一起封裝爲RequestMappingHandlerMapping
;
3、通過適配器執行請求
- 當獲取到處理器執行鏈
HandlerExecutionChain
後,就需要根據HandlerExecutionChain
中的Handler
—— 也就是根據被@RequestMapping
註解的方法得到的HandlerMethod
—— 找到對應的處理器適配HandlerAdapter
。DispatchServlet
中會註冊有多個HandlerAdapter
,但是最終只會根據Handler
找到唯一一個可用的HandlerAdapter
,一般情況下就是RequestMappingHandlerAdapter
; - 當調用
RequestMappingHandlerAdapter
的invokeHandlerMethod
方法後,HandlerMethod
會被包裝爲ServletInvocableHandlerMethod
,並分別爲其設置好以下幾個關鍵組件:- 參數解析器
HandlerMethodArgumentResolver
; - 返回值處理器
HandlerMethodReturnValueHandler
; - 視圖容器
ModelAndViewContainer
; - 異步管理器
WebAsyncManager
;
- 參數解析器
- 接着調用
ServletInvocableHandlerMethod
的invokeAndHandle
方法觸發整個執行流程:- 調用參數解析器
HandlerMethodArgumentResolver
獲取本次方法調用的真實參數,我們熟悉的一些註解就是在這一步完成處理的:@RequestParam
對應的處理器爲RequestParamMethodArgumentResolver
,它會從Request
請求中將請求參數與方法參數一一對應;@PathVariable
對應的處理器爲PathVariableMethodArgumentResolver
,它將會從請求路徑中提取參數並對應的方法參數中;@RequestBody
對應的處理器爲RequestResponseBodyMethodProcessor
,它將會從請求頭的 body 中讀取 json 參數並反序列爲方法參數對象,並且在這個過程中會執行RequestBodyAdvice
回調;
- 調用
HandlerMethod
,觸發用戶定義的業務邏輯,並獲得返回值; - 使用返回值處理器
HandlerMethodReturnValueHandler
處理HandlerMethod
調用後的返回值,根據返回值處理器的不同將會有不同的處理邏輯:- 如果我們的方法直接或間接被
@ResponseBody
註解,那麼RequestResponseBodyMethodProcessor
將會把方法的返回值序列化爲 json 字符串並寫入響應體中,並且在這個過程中會執行ResponseBodyAdvice
回調。最終不會往視圖容器ModelAndViewContainer
中放入任何View
; - 如果我們的方法返回
ModelAndView
對象,ModelAndViewMethodReturnValueHandler
則會把返回值中的View
扔到視圖容器ModelAndViewContainer
中;
- 如果我們的方法直接或間接被
- 調用參數解析器
4、異常處理
- 當執行
HandlerAdapter
的handle
方法的過程中拋出了異常,則異常將交由DispatchServlet
的processHandlerException
進行處理,該方法中將獲取可以用於處理該異常的異常解析器HandlerExceptionResolver
去處理異常。
異常攔截器有很多種,我們比較熟悉的@ControllerAdvice
+@ExceptionHandler
的方式實際上就是通過ExceptionHandlerExceptionResolver
完成的; - 當
ExceptionHandlerExceptionResolver
完成屬性初始化後,會觸發afterPropertiesSet
回調,在這個方法中,它將會:- 檢查所有被
@ControllerAdvice
註解的 bean 中的是否存在帶有@ExceptionHandler
註解的方法,若有則將該 bean 添加至緩存; - 加載默認的參數解析器
HandlerMethodArgumentResolver
與返回值處理器HandlerMethodReturnValueHandler
—— 這裏與與RequestMappingHandlerMapping
幾乎一樣,這也是爲什麼被@ExceptionHandler
註解的方法同樣支持@ResponseBody
等註解的原因;
- 檢查所有被
- 當通過
ExceptionHandlerExceptionResolver
的resolveException
方法處理異常時,它將會遍歷緩存中被@ControllerAdvice
註解的 bean,並檢查其中的所有方法:- 若方法有
@ExceptionHandler
註解,那麼就根據註解確認方法支持處理哪幾種類型的異常; - 若方法沒有註解,但是有
Throwable
類型的參數,就根據參數類型推測支持處理哪幾種異常; - 若方法沒有註解,且沒有
Throwable
類型的參數,就跳過它;
- 若方法有
所有符合條件的方法都將被包裝爲 ExceptionHandlerMethodResolver
並緩存在 ExceptionHandlerExceptionResolver
中;
- 當處理器異常時,會根據異常的類型去從緩存中尋找對應的
ExceptionHandlerMethodResolver
:- 若只找到一個,那麼就直接返回該
ExceptionHandlerMethodResolver
; - 若找到多個,則比較它們支持處理的異常類型的繼承樹中的深度,並返回深度最大的那個異常。
舉個例子,若現在又兩個處理器方法,一個支持Throwable
,而另一個支持RuntimeException
,那麼將優先選擇後者;
- 若只找到一個,那麼就直接返回該
- 若找到可用的
RequestMappingHandlerAdapter
,那麼將通過與RequestMappingHandlerAdapter
類似的流程走完對異常處理方法的調用,並最終使用返回的ModelAndView
(如果有)替換HandlerAdapter
得到的ModelAndView
。