转载请注明出处 ****https://cloud.tencent.com/developer/user/1605429
本系列文章主要根据源码讲解SpringMVC
的启动过程,以及相关重要组件的源码分析。阅读此系列文章需要具备Spring
以及SpringMVC
相关知识。本文将分以下几篇文章进行讲解,读者可按需查阅。
在前一篇文章SpringMVC 启动流程及相关源码分析中,详细探讨了Spring MVC
在Web容器
中部署后的启动过程,以及相关源码分析,同时也讨论了DispatcherServlet类
的初始化创建过程,相关内容在此不再赘述,如有需求可查阅。
本文主要讲解DispatcherServlet类
获取用户请求到响应的全过程,并针对相关源码进行分析。对于基本的MVC架构
本文不再进行讲解,有需要的读者可自行查阅。
首先,让我们站在Spring MVC
的四大组件:DispatcherServlet
、HandlerMapping
、HandlerAdapter
以及ViewResolver
的角度来看一下Spring MVC
对用户请求的处理过程,有如下时序图:
DispatcherServlet执行序列图
具体处理过程如下:
DispatcherServlet类
进行处理。DispatcherServlet类
遍历所有配置的HandlerMapping类
请求查找Handler
。HandlerMapping类
根据request请求
的URL
等信息查找能够进行处理的Handler
,以及相关拦截器interceptor
并构造HandlerExecutionChain
。HandlerMapping类
将构造的HandlerExecutionChain类
的对象返回给前端控制器DispatcherServlet类
。Handler
遍历所有配置的HandlerAdapter类
请求执行Handler
。HandlerAdapter类
执行相关Handler
并获取ModelAndView类
的对象。HandlerAdapter类
将上一步Handler
执行结果的ModelAndView 类
的对象返回给前端控制器。DispatcherServlet类
遍历所有配置的ViewResolver类
请求进行视图解析。ViewResolver类
进行视图解析并获取View
对象。ViewResolver类
向前端控制器返回上一步骤的View
对象。DispatcherServlet类
进行视图View
的渲染,填充Model
。DispatcherServlet类
向用户返回响应。通过时序图和上面的讲解不难发现,整个Spring MVC
对于用户请求的响应和处理都是以DispatcherServlet类
为核心,其他三大组件均与前端控制器进行交互,三大组件之间没有交互并且互相解耦,因此,三大组件可以替换不同的实现而互相没有任何影响,提高了整个架构的稳定性并且降低了耦合度。接下来会按照上述的响应过程逐一进行讲解。
DispatcherServlet类
本质上依旧是一个Servlet
并且其父类实现了Servlet接口
,我们知道,Servlet
执行Service()
方法对用户请求进行响应,根据前一篇文章的分析方法可以得到人如下的调用逻辑图:
service方法调用逻辑
从上图的源码调用逻辑可以看出,HttpServlet抽象类
实现了Servlet接口
的service(ServletRequest, ServletResponse)
的方法,因此,用户请求的第一执行方法为该方法,该方法紧接着直接调用了service(HttpServletRequest, HttpServletResponse)
方法,其子类FrameworkServlet抽象类
重写了该方法,因为多态的特性最终是调用了FrameworkServlet抽象类
的service(HttpServletRequest, HttpServletResponse)
方法,FrameworkServlet抽象类
同样也重写了doHead()
、doPost()
、doPut()
、doDelete()
、doOptions()
、doTrace()
方法,service(ServletRequest, ServletResponse)
方法根据请求类型的不同分别调用上述方法,上述六个方法都调用了processRequest()
方法,而该方法最终调用了DispatcherServlet类
的doService()
方法。通过层层分析,我们找到了最终要调用的处理用户请求的方法,doService()
之前的方法调用都比较简单,这里不再逐一来查看源码,有兴趣的读者可以自行查阅。
查看doService()
的源码如下:
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
/*
将当前Servlet的子IoC容器放入request请求中
由此,我们可以访问到当前IoC子容器以及根IoC容器中的Bean
*/
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
//真正进行用户请求的处理
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
doService()
方法主要进行一些参数的设置,并将部分参数放入request
请求中,真正执行用户请求并作出响应的方法则为doDispatch()
方法,查看doDispatch()
方法的源码如下:
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//用户的request请求
HttpServletRequest processedRequest = request;
//HandlerExecutionChain局部变量
HandlerExecutionChain mappedHandler = null;
//判断是否解析了文件类型的数据,如果有最终需要清理
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
//ModelAndView局部变量
ModelAndView mv = null;
//处理异常局部变量
Exception dispatchException = null;
try {
//检查是否包含文件等类型的数据
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//向HandlerMapping请求查找HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
//如果HandlerExecutionChain为null,则没有能够进行处理的Handler,抛出异常
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//根据查找到的Handler请求查找能够进行处理的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
//判断自上次请求后是否有修改,没有修改直接返回响应
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
/*
按顺序依次执行HandlerInterceptor的preHandle方法
如果任一HandlerInterceptor的preHandle方法没有通过则不继续进行处理
*/
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//通过HandlerAdapter执行查找到的handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//逆序执行HandlerInterceptor的postHandle方法
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 NestedServletException("Handler dispatch failed", err);
}
//渲染视图填充Model,如果有异常渲染异常页面
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//如果有异常按倒序执行所有HandlerInterceptor的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
//如果有异常按倒序执行所有HandlerInterceptor的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
//倒序执行所有HandlerInterceptor的afterCompletion方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
//如果请求包含文件类型的数据则进行相关清理工作
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
根据上述源码并结合文章开始讲解的DispatcherServlet类
结合三大组件对用户请求的处理过程不难理解相关处理流程。
doDispatch()
方法通过调用getHandler()
方法并传入reuqest
通过HandlerMapping
查找HandlerExecutionChain
,查看其源码如下:
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
getHandler()
方法遍历了开发者配置的所有HandlerMapping类
根据request
请求来查找HandlerExecutionChain
,从这里可以看出,Spring MVC
是支持用户配置多个HandlerMapping类
的,在处理用户请求时会逐一查找,找到后立即返回,因此,如果多个HandlerMapping类
都能够处理同一request
请求,只会返回第一个能够处理的HandlerMapping类
构造的HandlerExecutionChain
,所以在配置HandlerMapping类
时需要注意不要对同一请求多次进行处理,由于篇幅问题HandlerMapping类
如何具体查找Handler
并构造HandlerExecutionChain
的细节不在此进行讲解,如有兴趣可以查阅本系列文章的第三篇SpringMVC HandlerMapping源码分析。
如果没有找到对应的HandlerExecutionChain
对象,则会执行noHandlerFound()
方法,继续查看其源码如下:
/**
* No handler found -> set appropriate HTTP response status.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception if preparing the response failed
*/
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
如果没有找到对应的HandlerExecutionChain
则会抛出异常NoHandlerFoundException
,在开发的过程中,如果我们将具体的URL
写错了则会遇到这个404错误。
继续查看doDispatch()
方法的源码,如果找到了HandlerExecutionChain
接下来会调用getHandlerAdapter()
方法来查找能够对Handler
进行处理的HandlerAdapter
,查看其源码如下:
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
与HandlerMapping
类似,查找能够处理具体Handler
的HandlerAdapter
时同样会遍历所有配置了的HandlerAdapter
,HandlerAdapter
是一个接口包含一个support()
方法,该方法根据Handler
是否实现某个特定的接口来判断该HandlerAdapter
是否能够处理这个具体的Handler
,这里使用适配器模式,通过这样的方式就可以支持不同类型的HandlerAdapter
。如果没有查找到能够处理Handler
的HandlerAdapter
则会抛出异常,如果在开发的过程中Handler
在实现接口时出现了问题就可能会遇到上述异常。
查找到了对应的HandlerAdapter
后就会调用HandlerExecutionChain
的applyPreHandle()
方法来执行配置的所有HandlerInteceptor
的preHandle()
方法,查看其源码如下:
/**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
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;
}
HandlerExecutionChain
的applyPreHandle()
方法会按照顺序依次调用HandlerInterceptor
的preHandle()
方法,但当任一HandlerInterceptor
的preHandle()
方法返回了false
就不再继续执行其他HandlerInterceptor
的preHandle()
方法,而是直接跳转执行triggerAfterCompletion()
方法,查看该方法源码如下:
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
这里遍历的下标为interceptorIndex
,该变量在前一个方法applyPreHandle()
方法中赋值,如果preHandle()
方法返回true
该变量加一,因此该方法会逆序执行所有preHandle()
方法返回了true
的HandlerInterceptor
的afterCompletion()
方法。到这里读者已经掌握了HandlerInterceptor
的preHandle()
方法以及afterCompletion()
方法的执行顺序,这些内容并不需要我们死记,需要知道其执行顺序查看源码是最好的方法。
继续阅读doDispatch()
方法的源码,如果所有拦截器的preHandle()
方法都返回了true
没有进行拦截,接下来前端控制器会请求执行上文获取的Handler
,这个Handler
就是开发的时候编写的Controller
,根据实现接口的不同执行相关方法,并获取到ModelAndView类
的对象。
接下来会执行HandlerInterceptor
的postHandle()
方法,具体源码如下:
/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, 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()
方法是按照逆序执行。
执行完postHandle()
方法后,doDispatch()
方法调用了processDispatchResult()
方法,其源码如下:
/**
* 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,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
//判断HandlerMapping、HandlerAdapter处理时的异常是否为空
if (exception != null) {
//上述两个组件处理时的异常不为空
//如果为ModelAndViewDefiningException异常,则获取一个异常视图
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
//如果不为ModelAndViewDefiningException异常,进行异常视图的获取
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?
//判断mv是否为空,不管是正常的ModelAndView还是异常的ModelAndView,只要存在mv就进行视图渲染
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
//否则记录无视图
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
//执行相关HandlerInterceptor的afterCompletion()方法
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
该方法传入了一个异常类的对象dispatchException
,阅读doDispatch()
方法的源码可以看出,Spring MVC
对整个doDispatch()
方法用了嵌套的try-catch
语句,内层的try-catch
用于捕获HandlerMapping
进行映射查找HandlerExecutionChain
以及HandlerAdapter
执行具体Handler
时的处理异常,并将异常传入到上述processDispatchResult()
方法中。
processDispatchResult()
方法主要用于针对产生的异常来构造异常视图,接着不管视图是正常视图还是异常视图均调用render()
方法来渲染,查看render()
方法的具体源码如下:
/**
* Render the given ModelAndView.
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
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.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// We need to resolve the view name.
// 解析视图名称获取对应视图View
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
//如果视图View为空抛出异常
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.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
//设置Http响应状态字
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
//调用视图View的render方法通过Model来渲染视图
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
render()
方法通过调用resolveViewName()
方法根据视图名称解析对应的视图View
,该方法源码如下:
/**
* Resolve the given view name into a View object (to be rendered).
* <p>The default implementations asks all ViewResolvers of this dispatcher.
* Can be overridden for custom resolution strategies, potentially based on
* specific model attributes or request parameters.
* @param viewName the name of the view to resolve
* @param model the model to be passed to the view
* @param locale the current locale
* @param request current HTTP servlet request
* @return the View object, or {@code null} if none found
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
* @see ViewResolver#resolveViewName
*/
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
resolveViewName()
方法通过遍历配置的所有ViewResolver类
根据视图名称来解析对应的视图View
,如果找到则返回对应视图View
,没有找到则返回null
。
回到前一个render()
方法,如果上述方法返回的视图为null
则抛出异常,这个异常相信大多数人也见过,当开发时写错了返回的View
视图名称时就会抛出该异常。接下来调用具体视图的render()
方法来进行Model
数据的渲染填充,最终构造成完整的视图。
到这里,doDispatch()
的外层try-catch
异常的作用我们就知道了,为了捕获渲染视图时的异常,通过两层嵌套的try-catch
,Spring MVC
就能够捕获到三大组件在处理用户请求时的异常,通过这样的方法能够很方便的实现统一的异常处理。
通过前文的源码分析,我们能够清楚的认识到Spring MVC
对用户请求的处理过程,进一步加深对Spring MVC
的理解。
由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。