实例流程图
组成
-
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);