文章目錄
- 前言
- 12.4 DispatcherServlet的工作全流程
- 12.4.1 DispatcherServlet#service
- 12.4.2 processRequest
- 12.4.3 doService
- 12.4.3.1 isIncludeRequest的判斷
- 12.4.3.2 FlashMapManager的設計
- 12.4.4 doDispatch
- 12.4.4.1 處理文件上傳請求
- 12.4.4.2 獲取可用的Handler
- (1)HandlerMapping.getHandler
- (2)getHandlerInternal
- (3)getHandlerExecutionChain
- 12.4.4.3 獲取HandlerAdapter
- 12.4.4.4 回調攔截器
- 12.4.4.5 執行Handler
- (1)handleInternal
- (2)invokeHandlerMethod
- (a)初始化參數綁定器
- (b)參數預綁定
- (c)創建方法執行對象
- (d)執行Controller的方法```invokeAndHandle```
- (e)包裝ModelAndView
- 12.4.4.6 再次回調攔截器
- 12.4.4.7 處理視圖、解析異常
- (1)處理異常
- (2)渲染視圖
- (3)第三次回調攔截器
- 12.4.5 DispatcherServlet工作流程總結
- 12.5 小結
前言
WebMvc的核心組件裝配完成之后,DispatcherServlet作為WebMvc的核心前端控制器正式投入工作,默認接收客戶端的所有請求,并調度其它核心組件處理請求,最終響應結果給客戶端。
本節內容研究WebMvc在實際運行期間DispatcherServlet對于請求處理和響應結果的全流程執行原理。本文內容由于不可割裂,因此具有超長預警。
本文沿用 SpringBoot源碼解讀與原理分析(三十六)SpringBoot整合WebMvc(一)@Controller控制器裝配原理 12.1 SpringBoot整合WebMvc案例 中編寫好的示例項目,并進行一些小改動。
- 新增CustomAdvice類,標注@ControllerAdvice注解,并編寫兩個標注@InitBinder和@ExceptionHandler的方法
@ControllerAdvice
public class CustomAdvice {@InitBinderpublic void customDataBinder(WebDataBinder dataBinder) {// 請求URL中的時間格式為yyyy-MM-dd HH:mm:ss// 自動綁定到Date屬性參數中dataBinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));}@ExceptionHandler({Exception.class})public String customExceptionHandler(Exception ex) {System.out.println("自定義異常發生了," + ex.getMessage());return "自定義異常返回";}}
- 修改UserController類,新增標注@InitBinder注解的方法,
test
方法新增一個Date類型的參數
@Controller
@RequestMapping("/user")
public class UserController {@InitBinderpublic void myDataBinder(WebDataBinder dataBinder) {// 如果參數是字符串類型,則去除字符串的前后空格dataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(true));}@RequestMapping(method = RequestMethod.GET, value = "/test")@ResponseBodypublic String test(String name, Date time) {System.out.println("請求參數 name=" + name);System.out.println("請求參數 time=" + time);return name;}
}
- 新增攔截器CustomInterceptor類
public class CustomInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("攔截器的preHandle方法執行了...");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("攔截器的postHandle方法執行了...");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("攔截器的afterCompletion方法執行了...");}
}
- 修改配置類,加載攔截器及配置其路徑
@Configuration
public class PathConfig implements WebMvcConfigurer {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix("/api", c -> c.isAnnotationPresent(Controller.class));}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/api/user/test");}
}
至此,示例項目代碼編寫完成,執行主啟動類的main
方法啟動項目。使用瀏覽器訪問 http://127.0.0.1:8080/api/user/test?name=aaa&time=2024-02-29 12:12:12
,控制臺打印結果如下:
攔截器的preHandle方法執行了...
請求參數 name=aaa
請求參數 time=Thu Feb 29 12:12:12 CST 2024
攔截器的postHandle方法執行了...
攔截器的afterCompletion方法執行了...
12.4 DispatcherServlet的工作全流程
啟動示例項目后,在DispatcherServlet的父類FrameworkServlet的service
方法上打入斷點,隨后使用瀏覽器發起請求,待程序停在斷點處,開始Debug調試。
12.4.1 DispatcherServlet#service
源碼1:FrameworkServlet.javaprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (httpMethod == HttpMethod.PATCH || httpMethod == null) {// 對PATCH類型的請求單獨處理processRequest(request, response);} else {// 調用父類的HttpServlet的service方法super.service(request, response);}
}@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);
}
由 源碼1 可知,service
方法會對PATCH類型的請求單獨處理,但通常在項目開發中不會使用PATCH類型。
繼續向下執行else塊的super.service
方法,FrameworkServlet的父類HttpServlet會根據不同的請求類型將方法轉發至doXxx
方法中,所以最終執行的是FrameworkServlet中重寫的doGet
、doPost
、doPut
、doDelete
等方法,而這些方法最終都會調用processRequest
方法。
12.4.2 processRequest
源碼2:FrameworkServlet.javaprotected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 記錄接收請求的時間long startTime = System.currentTimeMillis();Throwable failureCause = null;// 獲取當前線程的LocaleContextLocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();// 創建當前線程的LocaleContextLocaleContext localeContext = buildLocaleContext(request);// 獲取當前線程的RequestAttributesRequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();// 創建當前線程的RequestAttributesServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());// 初始化ContextHolder,傳入新封裝好LocaleContext和RequestAttributesinitContextHolders(request, localeContext, requestAttributes);try {// 真正處理請求的方法,但這是子類的模板方法doService(request, response);} // catch ...finally {// 重新設置當前線程的LocaleContext和RequestAttributesresetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);// 發布ServletRequestHandledEvent事件publishRequestHandledEvent(request, response, startTime, failureCause);}
}private void initContextHolders(HttpServletRequest request,@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {// 將全新的LocaleContext和RequestAttributes設置到當前線程中if (localeContext != null) {LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);}if (requestAttributes != null) {RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);}
}private void resetContextHolders(HttpServletRequest request,@Nullable LocaleContext prevLocaleContext, @Nullable RequestAttributes previousAttributes) {// 將預先保存的LocaleContext和RequestAttributes設置回線程中LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
}
由 源碼2 可知,processRequest
方法會對請求做一些前置處理,再轉調doService
方法真正處理請求。
在該方法的前置處理中,做了線程之間的隔離。首先獲取當前線程的LocaleContext和RequestAttributes并暫存在方法中,隨后創建全新的LocaleContext和RequestAttributes,調用initContextHolders
方法設置到當前線程中,以此完成線程之間的隔離。
待請求完成處理后,在finally代碼塊中,調用resetContextHolders
方法將預先保存好的LocaleContext和RequestAttributes設置回線程中,以恢復原來的線程。
12.4.3 doService
源碼3:DispatcherServlet.javaprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);Map<String, Object> attributesSnapshot = null;// 判斷請求是否由<jsp:include>標簽而來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));}}}// ......if (this.flashMapManager != null) {FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}try {// 真正處理請求的方法doDispatch(request, response);} // finally ...
}
由 源碼3 可知,doService
方法也是在進行一些前置處理之后,轉調doDispatch
方法真正處理請求。
12.4.3.1 isIncludeRequest的判斷
源碼4:WebUtils.javapublic static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
public static boolean isIncludeRequest(ServletRequest request) {// 判斷當前請求中是否包含名為“javax.servlet.include.request_uri”的屬性return (request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE) != null);
}
由 源碼4 可知,doService
方法的第一個if判斷結構會調用WebUtils.isIncludeRequest(request)
方法。該方法會判斷當前請求中是否包含名為“javax.servlet.include.request_uri”的屬性。
在JSP中,使用 <jsp:incluedepage=“xxx.jsp”> 標簽可以組合其他JSP頁面,那么這個被組合的JSP頁面的加載請求就會帶上“javax.servlet.include.request_uri”屬性。因此,isIncludeRequest
方法的作用是區別頁面的加載是否由<jsp:include>標簽而來。
12.4.3.2 FlashMapManager的設計
在用戶登錄的業務場景中,如果是前后端不分離的情況,通常是使用POST請求將用戶名、密碼等信息傳入后端以供認證,認證成功后使用重定向將客戶端引導至系統主頁。
在這個前提下有一個特殊的場景:如果用于登錄時提交的登錄表單中,有一些需要在跳轉至主頁時渲染的數據,則僅放入request域中無法解決問題。
Spring引入FlashMapManager來解決這個問題,可以在頁面重定向發生跳轉時,將需要渲染的數據暫時放入session中,這樣瀏覽器即便刷新也不會影響數據渲染。
12.4.4 doDispatch
該方法是處理請求和響應的核心方法,由于篇幅很長,下面拆解來看。
12.4.4.1 處理文件上傳請求
源碼5:DispatcherServlet.javaprotected 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);// ......
}protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {// logger ...}} else if (hasMultipartException(request)) {// logger ...} else {try {return this.multipartResolver.resolveMultipart(request);} // catch ...}}return request;
}
源碼6:StandardServletMultipartResolver.java@Override
public boolean isMultipart(HttpServletRequest request) {return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
由 源碼5 可知,doDispatch
方法的第一部分邏輯是處理帶有文件上傳的請求,其核心組件是MultipartResolver。首先判斷請求是否是multipart請求,如果是,則將本由Servlet容器處理的HttpServletRequest對象轉換為可以訪問請求中的文件對象的MultipartHttpServletRequest子接口對象。
由 源碼6 可知,判斷請求是否是multipart請求的方法是判斷請求頭content-type是否以"multipart/"開頭。
由于當前Debug的請求只是一個普通的GET請求,所以不會進入該部分代碼。
12.4.4.2 獲取可用的Handler
源碼7:DispatcherServlet.javaprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 處理文件上傳的請求 ...// 獲取可用的HandlermappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// ......
}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;
}
由 源碼7 可知,doDispatch
方法的第二部分邏輯是獲取可用的Handler。在getHandler
方法中,遍歷所有的HandlerMapping,從中找出可以返回非空HandlerExecutionChain對象的HandlerMapping。
Debug至getHandler
方法的內部,可以發現有5個HandlerMapping可以選擇。由于實例項目編寫的Handler是以@Controller+@RequestMapping注解實現的,因此最終選擇出來的HandlerMapping是與之相匹配的RequestMappingHandlerMapping。
(1)HandlerMapping.getHandler
源碼8:AbstractHandlerMapping.javapublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 留給子類實現的模板方法Object handler = getHandlerInternal(request);// ......// 構建HandlerExecutionChain對象HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);// ......return executionChain;
}
由 源碼8 可知,HandlerMapping的getHandler
的方法的核心邏輯有兩步:由子類具體獲取Handler的具體對象;根據Handler對象構建HandlerExecutionChain對象。
(2)getHandlerInternal
Debug進入getHandlerInternal
方法,發現程序會進入RequestMappingHandlerMapping的直接父類RequestMappingInfoHandlerMapping中。
源碼9:RequestMappingInfoHandlerMapping.java@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);try {return super.getHandlerInternal(request);} finally {ProducesRequestCondition.clearMediaTypesAttribute(request);}
}
由 源碼9 可知,RequestMappingInfoHandlerMapping中的getHandlerInternal
也是直接使用super.getHandlerInternal
方法調用父類AbstractHandlerMethodMapping中的getHandlerInternal
方法。
源碼10:AbstractHandlerMethodMapping.java@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// 獲取本次請求的URI,并設置到request域中String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);request.setAttribute(LOOKUP_PATH, lookupPath);this.mappingRegistry.acquireReadLock();try {// 獲取可以處理當前URI請求的HandlerMethod對象HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);// 創建全新的HandlerMethod對象return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);} finally {this.mappingRegistry.releaseReadLock();}
}
由 源碼10 可知,getHandlerInternal
方法首先會借助UrlPathHelper獲取本次請求URI(示例項目是"/api/user/test"),并設置到request域中。隨后調用lookupHandlerMethod
方法,獲取可以處理當前URI請求的HandlerMethod對象(實例項目是UserController的test
方法)。
注意,獲取到HandlerMethod對象之后,還要調用createWithResolvedBean
方法創建一個全新的HandlerMethod對象。為什么還要再次創建呢?
注意觀察上圖,發現HandlerMethod對象中的bean屬性是一個字符串"userController",而不是BeanFactory中真實存在的UserController對象,其他屬性也沒有UserController對象的持有。
源碼11:HandlerMethod.javapublic HandlerMethod createWithResolvedBean() {Object handler = this.bean;if (this.bean instanceof String) {Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");String beanName = (String) this.bean;// 從BeanFactory中取出bean對象handler = this.beanFactory.getBean(beanName);}// 重新封裝HandlerMethodreturn new HandlerMethod(this, handler);
}
由 源碼11 可知,createWithResolvedBean
方法的作用就是從BeanFactory中取出UserController對象并重新封裝。
(3)getHandlerExecutionChain
回到 源碼8 ,getHandlerInternal
執行完后獲得HandlerMethod對象,然后將該HandlerMethod對象傳入getHandlerExecutionChain
方法,組合跟該Handler相關的攔截器,并封裝為執行鏈HandlerExecutionChain對象。
源碼12:AbstractHandlerMapping.javaprotected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {// 構造HandlerExecutionChain對象HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));// 獲取請求路徑String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);for (HandlerInterceptor interceptor : this.adaptedInterceptors) {if (interceptor instanceof MappedInterceptor) {// 匹配路徑的攔截器處理MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {chain.addInterceptor(mappedInterceptor.getInterceptor());}} else {// 普通攔截器直接添加chain.addInterceptor(interceptor);}}return chain;
}
由 源碼12 可知,getHandlerExecutionChain
會根據HandlerInterceptor的類型分別進行處理,最終構造一個HandlerExecutionChain對象。
由上圖可知,對示例項目來說,構造的HandlerExecutionChain對象組合了三個攔截器,分別是ConversionServiceExposingInterceptor、ResourceUrlProviderExposingInterceptor,以及示例項目自定義的CustomInterceptor。
獲得HandlerMethod對象并組合攔截器封裝成HandlerExecutionChain對象之后,HandlerMapping的工作全部完成,接下來回到DispatcherServlet的doDispatch
方法。
12.4.4.3 獲取HandlerAdapter
源碼13:DispatcherServlet.javaprotected 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 {// 處理文件上傳請求// 獲取可用的Handler// 獲取HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// ......
}protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}}}// throw ex ...
}
由 源碼13 可知,doDispatch
方法的第三部分邏輯是獲取可以處理當前請求的HandlerAdapter,匹配規則是通過HandlerAdapter的supports
方法。
Debug至getHandlerAdapter
方法,發現有4個可選的HandlerAdapter實現類對象:
由上圖可知,示例項目選擇的HandlerAdapter實現類對象是RequestMappingHandlerAdapter。
源碼14:AbstractHandlerMethodAdapter.java@Override
public final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
源碼15:RequestMappingHandlerAdapter.java@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {return true;
}
由 源碼14-15 可知,supports
方法僅是判斷Handler類是否是HandlerMethod對象,supportsInternal
方法默認返回true,此處supports
方法一定返回true,因此最終會選擇可選HandlerAdapter對象集合中下標為0的對象,即RequestMappingHandlerAdapter。
12.4.4.4 回調攔截器
源碼16:DispatcherServlet.javaprotected 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 {// 處理文件上傳請求// 獲取可用的Handler// 獲取HandlerAdapter// 回調攔截器if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// ......
}boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {// 獲取HandlerExecutionChain對象中組合的攔截器HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];// 遍歷攔截器,執行preHandle方法if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;
}
由 源碼16 可知,doDispatch
方法的第四部分邏輯是回調HandlerExecutionChain對象中組合的攔截器,首先會獲取HandlerExecutionChain對象中組合的攔截器,然后遍歷這些攔截器,執行其preHandle
方法。
注意,每個攔截器preHandle
方法如果有一個返回false,則applyPreHandle
方法直接返回false,doDispatch
方法直接結束不再執行下面的邏輯。
由 12.4.4.2 節的getHandlerExecutionChain
方法分析可知,HandlerExecutionChain對象中組合的攔截器有3個,分別是ConversionServiceExposingInterceptor和ResourceUrlProviderExposingInterceptor,以及示例項目自定義的CustomInterceptor。
源碼17:ResourceUrlProviderExposingInterceptor.java@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {try {request.setAttribute(RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider);} // catch ...return true;
}
源碼18:ConversionServiceExposingInterceptor.java@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws ServletException, IOException {request.setAttribute(ConversionService.class.getName(), this.conversionService);return true;
}
源碼19:CustomInterceptor.javapublic class CustomInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("攔截器的preHandle方法執行了...");return true;}
}
有 源碼17-19 以及CustomInterceptor類可知,四兩個攔截器的preHandle
方法一定會返回true,因此applyPreHandle
方法返回true,doDispatch
方法繼續執行下面的邏輯。
12.4.4.5 執行Handler
源碼20:DispatcherServlet.javaprotected 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 {// 處理文件上傳請求// 獲取可用的Handler// 獲取HandlerAdapter// 回調攔截器// 實際執行Handlermv = ha.handle(processedRequest, response, mappedHandler.getHandler());// ......
}
由 源碼20 可知,doDispatch
方法的第五部分邏輯是調用HandlerAdapter的```handle``方法實際執行Handler,返回一個ModelAndView。
借助IDE,可以發現handle
方法的實現在DispatcherServlet的父類AbstractHandlerMethodAdapter中,其handle
方法又會轉調子類RequestMappingHandlerAdapter的handleInternal
方法。
(1)handleInternal
源碼21:AbstractHandlerMethodAdapter.java@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);
}
源碼22:RequestMappingHandlerAdapter.javaprivate boolean synchronizeOnSession = false;
@Override
protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;checkRequest(request);// 同步session的配置,默認為fasleif (this.synchronizeOnSession) {// ......} else {// 執行Handler方法mav = invokeHandlerMethod(request, response, handlerMethod);}// ......return mav;
}
由 源碼21-22 可知,handleInternal
方法最核心的邏輯是else代碼塊中的invokeHandlerMethod
方法。
(2)invokeHandlerMethod
由于invokeHandlerMethod
方法的篇幅很長,下面拆解來看。
(a)初始化參數綁定器
在WebMvc中,有兩個和參數綁定相關的注解:@InitBinder和@ControllerAdvice。
@InitBinder注解可以單獨聲明在一個Controller類中,執行當前Controller類中的方法時,會先執行標注了@InitBinder注解的方法,初始化一些參數綁定器的邏輯。
@ControllerAdvice注解可以配合@InitBinder注解標注的方法,實現全局的參數綁定器預初始化。
源碼23:RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化參數綁定器WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);// ......
}public static final MethodFilter INIT_BINDER_METHODS = method ->AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {Class<?> handlerType = handlerMethod.getBeanType();Set<Method> methods = this.initBinderCache.get(handlerType);if (methods == null) {// 獲取當前Controller中全部標注了@InitBinder注解的方法,并放到initBinderCache緩存中methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);this.initBinderCache.put(handlerType, methods);}List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();// 遍歷標注了@ControllerAdvice注解的類this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {Object bean = controllerAdviceBean.resolveBean();// 遍歷這些類中的全部標注了@InitBinder注解的方法,添加到initBinderMethods緩存中for (Method method : methodSet) {initBinderMethods.add(createInitBinderMethod(bean, method));}}});// 遍歷當前Controller中全部標注了@InitBinder注解的方法// 將這些方法添加到initBinderMethods緩存中// 相當于合并了Controller類中和標注了@ControllerAdvice注解的類中的方法for (Method method : methods) {Object bean = handlerMethod.getBean();initBinderMethods.add(createInitBinderMethod(bean, method));}// 創建數據綁定工廠return createDataBinderFactory(initBinderMethods);
}
底層收集和執行@InitBinder注解的方法就是invokeHandlerMethod
方法中的getDataBinderFactory
方法。
由 源碼23 可知,getDataBinderFactory
方法會分別收集Controller類中標注了@InitBinder注解的方法、標注了@ControllerAdvice注解的類中標注了@InitBinder注解的方法,并合并起來共同組成參數綁定器。
示例項目中編寫的兩類綁定器都被收集起來了,如圖:
在UserController中定義的參數綁定器是:
@InitBinder
public void myDataBinder(WebDataBinder dataBinder) {// 如果參數是字符串類型,則去除字符串的前后空格dataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
如果暫時把@InitBinder注解注釋掉,該綁定器不再生效,執行結果如下:
如果添加了把@InitBinder注解注解,該綁定器生效,執行結果如下:
CustomAdvice類中定義的參數綁定器是:
@ControllerAdvice
public class CustomAdvice {@InitBinderpublic void customDataBinder(WebDataBinder dataBinder) {// 請求URL中的時間格式為yyyy-MM-dd HH:mm:ss// 自動綁定到Date屬性參數中dataBinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));}
}
如果時間類型的參數不按照 yyyy-MM-dd HH:mm:ss 的格式傳遞,在瀏覽器訪問:http://127.0.0.1:8080/api/user/test?name=aaa&time=2024年2月29日 12時12分12秒
,結果如下:
IllegalArgumentException: Parse attempt failed for value [2024年2月29日 12時12分12秒]
如果時間類型的參數按照 yyyy-MM-dd HH:mm:ss 的格式傳遞,在瀏覽器訪問:http://127.0.0.1:8080/api/user/test?name=aaa&time=2024-02-29 12-12-12
,則正常訪問。
通過以上測試,發現自定義的參數綁定器均生效了。
(b)參數預綁定
源碼24:RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化參數綁定器 ...// 參數預綁定ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// ......
}public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->(!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);Class<?> handlerType = handlerMethod.getBeanType();Set<Method> methods = this.modelAttributeCache.get(handlerType);if (methods == null) {// 獲取當前Controller中全部標注了@ModelAttribute注解且沒有被@RequestMapping注解標注的方法// 并放到modelAttributeCache緩存中methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);this.modelAttributeCache.put(handlerType, methods);}List<InvocableHandlerMethod> attrMethods = new ArrayList<>();// 遍歷標注了@ControllerAdvice注解的類this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> {if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {Object bean = controllerAdviceBean.resolveBean();// 遍歷這些類中的全部標注了@ModelAttribute注解的方法,添加到attrMethods緩存中for (Method method : methodSet) {attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));}}});// 遍歷當前Controller中全部標注了@ModelAttribute注解的方法// 將這些方法添加到attrMethods緩存中// 相當于合并了Controller類中和標注了@ControllerAdvice注解的類中的方法for (Method method : methods) {Object bean = handlerMethod.getBean();attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));}return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
由 源碼24 可知,getModelFactory
方法和上一步的getDataBinderFactory
方法的邏輯結構幾乎一樣,都是將Controller類中的標注了某個注解的方法,與全局類中標注了某個注解的方法合并起來。
不同的是,getDataBinderFactory
方法找的是@InitBinder注解,而getModelFactory
方法找的是@ModelAttribute注解;相同的是,全局類都標注@ControllerAdvice注解。
(c)創建方法執行對象
源碼25:RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化參數綁定器 ...// 參數預綁定 ...// 創建方法執行對象ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);// ......
}protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {return new ServletInvocableHandlerMethod(handlerMethod);
}
由 源碼25 可知,createInvocableHandlerMethod
方法只是將HandlerMethod對象二度封裝為ServletInvocableHandlerMethod對象。
(d)執行Controller的方法invokeAndHandle
源碼26:RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化參數綁定器 ...// 參數預綁定 ...// 創建方法執行對象 ...// 創建ModelAndView的容器ModelAndViewContainer ...(不重要)// 對異步請求的支持 ...(不重要)// 執行Controller的方法invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}// 包裝ModelAndViewreturn getModelAndView(mavContainer, modelFactory, webRequest);} // finally ...
}
由 源碼26 可知,創建方法執行對象ServletInvocableHandlerMethod后,又創建了ModelAndView的容器ModelAndViewContainer,以及處理了對異步請求的支持,最后調用方法執行對象ServletInvocableHandlerMethod的invokeAndHandle
方法執行Handler方法。
由于invokeAndHandle
方法的篇幅很長,下面拆解來看。
- (i)反射執行Controller方法
源碼27:ServletInvocableHandlerMethod.javapublic void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 反射執行Controller方法Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);// ......
}
源碼28:InvocableHandlerMethod.javapublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 獲取參數值列表Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);// logger ...return doInvoke(args);
}protected Object doInvoke(Object... args) throws Exception {ReflectionUtils.makeAccessible(getBridgedMethod());try {// 利用反射機制執行Handlerreturn getBridgedMethod().invoke(getBean(), args);}// catch ...
}
由 源碼27-28 可知,invokeAndHandle
方法調用invokeForRequest
方法,會先收集好執行Handler方法所需的參數列表,然后利用反射機制執行目標Handler方法。doInvoke
方法執行完畢后,意味著項目中編寫的Controller類的方法已經執行完畢。
- (ii)處理方法返回值
源碼29:ServletInvocableHandlerMethod.javapublic void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 反射執行Controller方法Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);// ......// 處理方法返回值try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);} // catch ...
}
源碼30:HandlerMethodReturnValueHandlerComposite.java@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 篩選出可以處理當前Controller返回的HandlerMethodReturnValueHandler對象HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {// throw ......}handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {boolean isAsyncValue = isAsyncReturnValue(value, returnType);// 遍歷HandlerMethodReturnValueHandler集合for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}// 找出支持當前返回值類型的if (handler.supportsReturnType(returnType)) {return handler;}}return null;
}
由 源碼29-30 可知,處理方法返回值的handleReturnValue
方法會先篩選出可以處理當前Controller返回的HandlerMethodReturnValueHandler對象,再調用其handleReturnValue
方法處理返回值。
篩選HandlerMethodReturnValueHandler對象的selectHandler
方法,是利用for循環遍歷HandlerMethodReturnValueHandler對象集合,找出其中支持當前返回值類型的HandlerMethodReturnValueHandler對象。
借助Debug工具,可以發現可選的HandlerMethodReturnValueHandler對象有15個,示例項目選擇的是RequestResponseBodyMethodProcessor。
在示例項目中,UserController的test
方法上標注了@ResponseBody注解,因此響應的是JSON數據。處理JSON數據響應的底層實現就是RequestResponseBodyMethodProcessor。
源碼31:RequestResponseBodyMethodProcessor.java@Override
public boolean supportsReturnType(MethodParameter returnType) {return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||returnType.hasMethodAnnotation(ResponseBody.class));
}
由 源碼31 可知,RequestResponseBodyMethodProcessor中supportsReturnType
方法就是判斷方法所在類或者方法上是否標注@ResponseBody注解。再一次印證RequestResponseBodyMethodProcessor是處理JSON數據響應的底層實現。
- (iii)處理JSON數據響應
獲取RequestResponseBodyMethodProcessor對象后,調用其handleReturnValue
方法正式處理返回值。
源碼32:RequestResponseBodyMethodProcessor.java@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// 使用JSON序列化的方式將方法返回的數據轉化為文本// 并寫入HttpServletResponse的輸出流中writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
由 源碼32 可知,handleReturnValue
方法會執行writeWithMessageConverters
方法,使用JSON序列化的方式將方法返回的數據轉化為文本,并寫入HttpServletResponse的輸出流中。
- (iiii)處理視圖返回
示例項目中是響應JSON數據,但在實際開發中也有可能是響應視圖。對于返回視圖和返回JSON數據,底層使用的HandlerMethodReturnValueHandler并不相同。
如果一個Controller方法要跳轉視圖,則方法的返回值一定是一個字符串,并且方法和類上都沒有標注@ResponseBody注解,這種情況下底層使用的HandlerMethodReturnValueHandler是ViewNameMethodReturnValueHandler。
源碼33:ViewNameMethodReturnValueHandler.java@Override
public boolean supportsReturnType(MethodParameter returnType) {Class<?> paramType = returnType.getParameterType();// 判斷返回值類型是否是CharSequencereturn (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {if (returnValue instanceof CharSequence) {String viewName = returnValue.toString();// 將String類型的返回值作為邏輯視圖名放入ModelAndView容器中mavContainer.setViewName(viewName);if (isRedirectViewName(viewName)) {mavContainer.setRedirectModelScenario(true);}} else if (returnValue != null) {// should not happen// throw ...}
}
由 源碼33 可知,處理視圖響應的handleReturnValue
方法會將String類型的返回值作為邏輯視圖名稱,并放入ModelAndView容器ModelAndViewContainer對象中。
至此,invokeAndHandle
方法執行完畢,Controller類中的方法已執行,ModelAndViewContainer中已經封裝了響應視圖名稱,或者將需要響應的數據寫入了HttpServletResponse中。
(e)包裝ModelAndView
源碼34:RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化參數綁定器 ...// 參數預綁定 ...// 創建方法執行對象 ...// 創建ModelAndView的容器ModelAndViewContainer ...(不重要)// 對異步請求的支持 ...(不重要)// 執行Controller的方法 ...// 包裝ModelAndViewreturn getModelAndView(mavContainer, modelFactory, webRequest);} // finally ...
}private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {// ......// 取出容器中的ModelMapModelMap model = mavContainer.getModel();// 取出視圖名稱,構造ModelAndViewModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());// ......return mav;
}
由 源碼34 可知,invokeHandlerMethod
方法的最后一步是調用getModelAndView
方法包裝ModelAndView對象。在該方法在中會從上一步封裝的ModelAndViewContainer對象中取出ModelMap和視圖名稱,構造成ModelAndView對象并返回。
簡言之,getModelAndView
方法完成了ModelAndViewContainer對象到ModelAndView對象的轉換。
至此,invokeHandlerMethod
方法執行完畢,HandlerAdapter的工作全部完成,流程回到DispatcherServlet的doDispatch
方法。
12.4.4.6 再次回調攔截器
源碼35:DispatcherServlet.javaprotected 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 {// 處理文件上傳請求 ...// 獲取可用的Handler ...// 獲取HandlerAdapter ...// 回調攔截器 ...// 執行Handler ...// 再次回調攔截器mappedHandler.applyPostHandle(processedRequest, response, mv);// ......
}
源碼36:HandlerExecutionChain.javavoid 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];interceptor.postHandle(request, response, this.handler, mv);}}
}
由 源碼35-36 可知,再次回調攔截器的applyPostHandle
方法和 12.4.4.4 節的applyPreHandle
的邏輯是幾乎一樣,不一樣的是,這次回調攔截器的順序是反過來的,調用的是攔截器的postHandle
方法。
12.4.4.7 處理視圖、解析異常
源碼37:DispatcherServlet.javaprotected 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 {// 處理文件上傳請求 ...// 獲取可用的Handler ...// 獲取HandlerAdapter ...// 回調攔截器 ...// 執行Handler ...// 再次回調攔截器 ...mappedHandler.applyPostHandle(processedRequest, response, mv);} // catch ...// 處理視圖、解析異常processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);// ......
}
由 源碼37 可知,DispatcherServlet的doDispatch
方法的最后一個關鍵步驟是processDispatchResult
方法,該方法會進行視圖處理,以及解析整個請求處理中拋出的異常。
該方法分為三個步驟,下面拆解來看。
(1)處理異常
源碼38:DispatcherServlet.javaprivate 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 ...mv = ((ModelAndViewDefiningException) exception).getModelAndView();} else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// ......
}
由 源碼38 可知,processDispatchResult
會根據異常的類型做不同的處理。
DispatcherServlet在處理客戶端發起的請求時,中間調用Controller或者Service等組件時拋出的異常都幾乎不可能是ModelAndViewDefiningException(前面的源碼分析也沒有見過這個異常),因此if代碼塊不重要。
在else代碼塊中,會調用processHandlerException
方法,該方法才是處理異常的核心方法。
源碼39:DispatcherServlet.javaprotected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// ......// 從HandlerExceptionResolver集合中找到可以處理當前異常的// 構造成ModelAndView對象并返回ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}// ......throw ex;
}
由 源碼39 可知,processHandlerException
方法會從HandlerExceptionResolver集合中找到可以處理當前異常的,構造成ModelAndView對象并返回。
在示例項目中,CustomAdvice類標注了@ControllerAdvice注解,并且聲明了標注@ExceptionHandler注解的方法,可實現全局統一異常處理。
@ControllerAdvice
public class CustomAdvice {@ExceptionHandler({Exception.class})public String customExceptionHandler(Exception ex) {System.out.println("自定義異常發生了," + ex.getMessage());return "自定義異常返回";}}
在UserController的test
方法加一行代碼:int i = 1/0
,發起請求時一定會拋出異常,控制臺的結果如下:
自定義異常發生了,/ by zero
可見,全局統一異常處理生效了。
(2)渲染視圖
源碼40:DispatcherServlet.javaprivate void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {// 處理異常 ...// 渲染視圖if (mv != null && !mv.wasCleared()) {render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}// ......
}protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// 國際化處理 ...View view;String viewName = mv.getViewName();if (viewName != null) {// 根據視圖名解析出視圖view = resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {// throw ...}} else {// 否則,直接獲取視圖;如果還沒有視圖,則拋出異常view = mv.getView();if (view == null) {// throw ...}}// logger ...try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}// 渲染視圖view.render(mv.getModelInternal(), request, response);} // catch ...
}
由 源碼40 可知,無論是正常響應還是拋出異常,最終都會生成一個ModelAndView對象,緊接著就要進行視圖的渲染,而渲染視圖的核心方法是render
方法。
渲染視圖的render
方法會從ModelAndView中獲取邏輯視圖的名稱,如果有名稱則借助ViewResolver去匹配視圖,如果成功匹配到則返回,如果匹配不到則拋出異常。匹配生成視圖View對象后,執行view.render
方法實際渲染視圖。
(3)第三次回調攔截器
源碼41:DispatcherServlet.javaprivate void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {// 處理異常 ...// 渲染視圖 ...// 第三次回調攔截器if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, null);}
}
源碼42:HandlerExecutionChain.javavoid triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {// 遍歷攔截器,執行其afterCompletion方法for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];try {interceptor.afterCompletion(request, response, this.handler, ex);} // catch ...}}
}
由 源碼41-42 可知,視圖渲染完成后,最后的收尾動作是回調所有攔截器的afterCompletion
方法。
這已經是第三次回調攔截器了,第一次執行攔截器的preHandle
方法,第二次倒序執行攔截器的postHandle
方法,第三次執行攔截器的afterCompletion
方法。其中,第一次是在Handler方法執行之前執行的,第二次和第三次是在之后執行的。
因此,控制臺的輸出如下:
攔截器的preHandle方法執行了...
請求參數 name=aaa
請求參數 time=Thu Feb 29 12:12:12 CST 2024
攔截器的postHandle方法執行了...
攔截器的afterCompletion方法執行了...
至此,processDispatchResult
方法執行完畢,DispatcherServlet的doDispatch
方法也執行完畢,一次完整的DispatcherServlet請求處理和響應完成了。
12.4.5 DispatcherServlet工作流程總結
DispatcherServlet工作全流程示意圖如下:
1. 客戶端向服務端發起請求,由DispatcherServlet接收請求;
2. DispatcherServlet委托HandlerMapping,根據本次請求的URL匹配合適的Controller方法;
3. HandlerMapping找到合適的Controller方法后,組合可以應用于當前請求的攔截器,并封裝為一個HandlerExecutionChain對象返回給DispatcherServlet;
4. DispatcherServlet接收到HandlerExecutionChain對象后,委托HandlerAdapter,將該請求轉發給選定的Handler;
5. Handler接收到請求后,實際執行Controller方法;
6. Controller方法執行完畢后返回一個ModelAndView對象給HandlerAdapter;
7. HandlerAdapter接收到ModelAndView對象后返回給DispatcherServlet;
8. DispatcherServlet接收到ModelAndView對象后,委托ViewResolver進行視圖渲染;
9. ViewResolver視圖渲染完成后返回給DispatcherServlet,由DispatcherServlet負責響應視圖。
12.5 小結
第12章到此就梳理完畢了,本章的主題是:SpringBoot整合WebMvc。回顧一下本章的梳理的內容:
(三十六)SpringBoot整合WebMvc(一)@Controller控制器裝配原理
(三十七)SpringBoot整合WebMvc(二)DispatcherServlet的工作全流程
更多內容請查閱分類專欄:SpringBoot源碼解讀與原理分析
第13章主要梳理:SpringBoot整合WebFlux。主要內容包括:
- 響應式編程與Reactive;
- SpringBoot整合WebFlux的快速使用;
- SpringBoot整合WebFlux的核心自動裝配;
- DispatcherHandler的工作全流程。