實例流程圖
組成
-
DispatcherServlet:HTTP請求處理程序/控制器的中央分配器,Spring MVC 項目的入口。
-
HandlerMapping:處理器映射
根據 request 找到請求對應的 HandlerExecutionChain
@Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
-
HandlerExecutionChain:處理器執行鏈
-
包含了對 Controller 進行包裝的處理器 handler(一般是 HandlerMethod 對象)
- HandlerMethod:封裝由方法和bean組成的處理程序方法的信息。提供對方法參數、方法返回值、方法註釋等的方便訪問。其中的方法在後續使用 InvocableHandlerMethod 的 doInvoke 方法進行反射的時候會被調用到。
-
包含了攔截器數組和攔截器列表,配合攔截器的 preHandle / postHandle / afterCompletion 可以對 handler 進行前置處理、後置處理、資源清理等增強操作。
-
preHandle
調用時間:Controller方法處理之前
執行順序:鏈式Intercepter情況下,Intercepter按照聲明的順序一個接一個執行
若返回false,則中斷執行,注意:不會進入afterCompletion
-
postHandle
調用前提:preHandle返回true
調用時間:Controller方法處理完之後,DispatcherServlet進行視圖的渲染之前,也就是說在這個方法中你可以對ModelAndView進行操作
執行順序:鏈式Intercepter情況下,Intercepter按照聲明的順序倒着執行。
備註:postHandle雖然post打頭,但post、get方法都能處理
-
afterCompletion
調用前提:preHandle返回true
調用時間:DispatcherServlet進行視圖的渲染之後
-
-
-
HandlerAdapter:處理器適配器
- 其中最重要的 handle 方法可以返回一個 ModelAndView 對象。
- supports 方法可以判斷是否支持傳入的處理器類型,如 HandlerMethod 等。
-
ModelAndView:這個類只是同時持有模型和視圖,使控制器能夠在一個返回值中同時返回模型和視圖。
-
ViewResolver:視圖解析器,其resolveViewName 方法會根據 viewName 解析出相應的 View 類型,例如是 ThymeleafView 或 JSP View.
新建項目
本文目的在於通過一個簡單的 Spring Boot 案例來解讀 Spring MVC 底層源碼。
-
在 IntelliJ IDEA 中基於
Spring Boot
+Spring Web
+Thymeleaf
+Lombok
新建一個項目。 -
在
model
包下創建Person
@Data @AllArgsConstructor public class Person { private String name; private Integer age; }
-
在
controller
包下創建PersonController
@Controller @RequestMapping("persons") public class PersonController { @GetMapping("") public ModelAndView getPersons() { List<Person> persons = new ArrayList<>(); persons.add(new Person("Jake", 27)); persons.add(new Person("Heather", 27)); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("persons", persons); modelAndView.setViewName("person"); return modelAndView; } @GetMapping("json") @ResponseBody public List<Person> getPersonsJson() { List<Person> persons = new ArrayList<>(); persons.add(new Person("Jake", 27)); persons.add(new Person("Heather", 27)); return persons; } }
-
在
resources/templates
路徑下創建person.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>人員</title> </head> <body> <table> <thead> <tr> <th>姓名</th> <th>年齡</th> </tr> </thead> <tbody> <tr th:each="person:${persons}"> <td th:text="${person.name}"></td> <td th:text="${person.age}"></td> </tr> </tbody> </table> </body> </html>
源碼詳解
ModelAndView
這種情況是在控制層返回ModelAndView
對象,會在前端看到渲染的視圖。
-
發起請求 http://localhost:8080/persons
-
入口
DispatcherServlet
的doService(HttpServletRequest request, HttpServletResponse response)
-
請求分發
doDispatch(HttpServletRequest request, HttpServletResponse response)
-
根據請求獲取映射處理器
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
-
在
handlerMappings
中找到以當前 request 作爲 key 的handlerMapping
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; }
-
由 Debug 模式看出
handlerMappings
中包含的元素如下:handlerMappings = {ArrayList@5507} size = 5 0 = {RequestMappingHandlerMapping@6466} 1 = {BeanNameUrlHandlerMapping@6467} 2 = {RouterFunctionMapping@6468} 3 = {SimpleUrlHandlerMapping@6469} 4 = {WelcomePageHandlerMapping@6470}
-
對於當前請求,進入
if(handler != null)
判斷時,對應的 mapping 和 handler 分別是:mapping = {RequestMappingHandlerMapping@6466}
handler = {HandlerExecutionChain@6534} "HandlerExecutionChain with [com.jake.spring.mvc.controller.PersonController#getPersons()] and 2 interceptors" handler = {HandlerMethod@6544} "com.jake.spring.mvc.controller.PersonController#getPersons()" interceptors = null interceptorList = {ArrayList@6545} size = 2 0 = {ConversionServiceExposingInterceptor@5577} 1 = {ResourceUrlProviderExposingInterceptor@5578} interceptorIndex = -1
說明此時使用的
HandlerMapping
是RequestMappingHandlerMapping
,其對應的handler
是一個HandlerExecution
對象,該對象中還有一個HandlerMethod
類型的handler
-
-
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
-
getHandlerAdapter
方法protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
-
由 Debug 可知, handler,即
mappedHandler.getHandler()
的值是一個HandlerMethod
對象handler = {HandlerMethod@6544}com.jake.spring.mvc.controller.PersonController#getPersons()
-
此時的
this.handlerAdapters
爲:this.handlerAdapters = {ArrayList@5508} size = 4 0 = {RequestMappingHandlerAdapter@5604} 1 = {HandlerFunctionAdapter@5605} 2 = {HttpRequestHandlerAdapter@5606} 3 = {SimpleControllerHandlerAdapter@5607}
-
在進入
if (adapter.supports(handler))
時,得到的adapter
是adapter = {RequestMappingHandlerAdapter@5604}
這裏的 supports 方法其實就是一個類型判斷,以
HttpRequestHandlerAdapter
爲例:@Override public boolean supports(Object handler) { return (handler instanceof HttpRequestHandler); }
-
-
語句執行完成後,
ha = {RequestMappingHandlerAdapter@5569}
-
判斷是否要應用
HandlerExecutionChain
的變量mappedHandler
的前置處理器,如果不應用直接返回。if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
HandlerExecutionChain
的applyPreHandle
方法boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } } return true; }
-
調用
mappedHandler
的handler
// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
進入
AbstractHandlerMethodAdapter
的handle
方法:@Override @Nullable public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); }
由 Debug 可知,
handler=com.jake.spring.mvc.controller.PersonController#getPersons()
-
此方法會調用當前類對象的
handleInternal
方法,而handleInternal
方法只有一個類RequestMappingHandlerAdapter
實現,其又調用了invokeHandlerMethod
方法。invocableMethod.invokeAndHandle(webRequest, mavContainer);
-
進入
InvocableHandlerMethod
的invokeAndHandle
方法,此方法又調用了doInvoke
方法,這個方法中真正實現了反射操作。 -
進入
PersonController
的getPersons
方法,封裝好ModelAndView
對象並返回 -
再次回到
DispatcherServlet
的doDispatch
方法,此時mv = {ModelAndView@6338} "ModelAndView [view="person"; model={persons=[Person(name=Jake, age=27), Person(name=Heather, age=27)]}]"
-
調用
applyDefaultViewName
給沒有viewName
的request
分配一個默認viewName
-
調用
mappedHandler.applyPostHandle(processedRequest, response, mv)
做後置處理HandlerExecutionChain
的applyPostHandle
方法/** * Apply postHandle methods of registered interceptors. */ void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this.handler, mv); } } }
postHandle
方法,顧名思義就是在當前請求進行處理之後,也就是 Controller 方法調用之後執行,但是它會在DispatcherServlet
進行視圖返回渲染之前被調用,所以我們可以在這個方法中對 Controller 處理之後的ModelAndView
對象進行操作。 -
調用
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)
/** * Handle the result of handler selection and handler invocation, which is * either a ModelAndView or an Exception to be resolved to a ModelAndView. */ 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; } if (mappedHandler != null) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); } }
-
調用本類(
DispatcherServlet
)的render
方法,其作用是渲染傳入的ModelAndView
對象protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); View view; String viewName = mv.getViewName(); if (viewName != null) { // We need to resolve the view name. view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } // Delegate to the View object for rendering. if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } }
-
調用本類中的
resolveViewName
方法得到相應的view
@Nullable protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } return null; }
在進入
if(view != null)
時,Debug 中觀察到的變量如下:viewName = "person" model = {ModelMap@6527} size = 1 "persons" -> {ArrayList@6492} size = 2 locale = {Locale@6428} "zh_CN" request = {RequestFacade@5508} viewResolver = {ContentNegotiatingViewResolver@6540} view = {ThymeleafView@6541}
這裏使用的
ViewResolver
對象是ContentNegotiatingViewResolver
類型的,另外,由於我們當前採用的是Thymeleaf
框架,所以View
對象的實現類是ThymeleafView
,該類是thymeleaf-spring5.jar
中的一個類。再調用 view 的 render 方法,呈現指定模型的視圖,此方法的第一步是準備請求,以渲染 JSP 爲例,這意味着將模型對象設置爲請求屬性;第二步是視圖的實際呈現,例如通過 RequestDispatcher 包含 JSP。 -
渲染完成後,斷點回到了
processDispatchResult
,執行mappedHandler.triggerAfterCompletion(request, response, null)
來進行資源清理。 -
如果在
doDispatch
時捕獲異常或錯誤,也會觸發HandlerInterceptor
的afterCompletion
方法來進行資源清理。 -
經過上述一系列步驟後,在瀏覽器中可以看到
ResponseBody
這種情況直接返回 JSON 數據給前端
-
訪問 http://localhost:8080/persons/json
-
這種情況
doDispatch
中的ModelAndView
對象mv
爲null
,其他均與返回視圖的情況一樣。 -
在瀏覽器中可以看到
過程總結
可以將上述的 Spring MVC 訪問實例以流程圖 + 源碼形式簡單總結:
-
客戶端發起請求,到達
DispatchServlet
的doService
方法,然後進入doDispatch
方法。 -
調用
getHandler
方法,在此方法中由已初始化的HandlerMapping
對象根據request
獲取HandlerExecutionChain
對象。mappedHandler = getHandler(processedRequest);
-
調用
getHandlerAdapter
方法,根據HandlerExecutionChain
對象中的處理器handler
來獲取HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
執行
HandlerAdapter
對象的handle
方法,獲取ModelAndView
對象mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
mv
返回給DispatchServlet
,以便其繼續利用mv
來進行後置處理和渲染。 -
利用
mv
來進行視圖解析processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
render(mv, request, response);
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
View view = viewResolver.resolveViewName(viewName, locale);
-
將模型渲染到前端視圖
view.render(mv.getModelInternal(), request, response);