springmvc最核心代码流程简单讲解

206 阅读13分钟

在Servlet标准中,请求的处理过程是先通过由所有的Filter组件构成Filter链对请求进行过滤与预处理了,如果在Filter链中没有对请求提前结束处理,则最终会进入Servlet组件中对请求进行处理;

请求一个get方法

image.png

由这个调用栈可以看到,过滤器执行完成后,就会调用HttpServlet的service方法

public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException {

    HttpServletRequest  request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException(lStrings.getString("http.non_http"));
    }
    service(request, response);
}

点击service方法,由子类实现:即FrameworkServlet,

protected void service(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
   if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
      processRequest(request, response);
   }
   else {
      super.service(request, response);
   }
}

image.png

这里httpMethod,我请求的是get方法,所以会走到else方法中,
protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // Invalid date header - proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }

    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);

    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);

    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);

    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);

    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);

    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //

        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

GET请求的处理有些特殊,进行了HTTP缓存规范判断,如判断请求头中的If-Modified-Since以达到直接从浏览器缓存获取请求结果,不用再去执行方法获取数据的目的;但前提是需要支持LastModified功能,默认为-1即不支持;则走doGet()方法;由子类FrameworkServlet实现

protected final void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   processRequest(request, response);
}

 

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 NestedServletException("Request processing failed", ex);
   }

   finally {
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
         requestAttributes.requestCompleted();
      }
      logResult(request, response, failureCause, asyncManager);
      publishRequestHandledEvent(request, response, startTime, failureCause);
   }
}

主要是这个doService(request, response);

image.png

这个doService方法也是由子类去实现的,由图可知,子类就是DispatcherServlet;

  doService方法中也有一个比较重要的点,Map<String, Object> attributesSnapshot = null;

保存请求属性快照用于请求完成后回复

在请求类型为include时,保存当前请求属性的快照,以便在include执行完后恢复请求属性;
目的:
在ServletApi中,请求类型常见的由重定向redirect,转发forward和包含include。重定向通过返回重定向响应实现;转发则直接丢弃当前处理过程,通过服务器内部转发给另一个请求处理流程来处理;而包含则时在一个请求处理过程中包含另一个请求的处理过程,同时还包含请求过程的过处理使用相同的request和response,这样可以做到在一个请求的响应中包含多个内部请求的响应结果;
比如:
1. jsp中的include标签;
2. 请求/root的处理过程中,先进入doService方法,向request中添加一些属性,该请求还在处理中,这时候服务器内部发起一个include请求,路径为/root/include,该include请求也会进入到doService方法,此时先保存了/root请求的属性,再执行/root/include的请求向request中添加属性,这样,再/root/include结束后,就是用快照恢复/root的属性,/root请求的属性就不会丢失;注意这里/root的结束在/root/include后边;

然后调用doDispatch(request, response);方法;
这个方法就是springmvc的最核心方法了;

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

//定义一个已处理请求,指向参数中的request,已处理请求后续可能会改变,主要用于比较
   HttpServletRequest processedRequest = request;

//定义处理器执行链,内部封装了拦截器列表和处理器
   HandlerExecutionChain mappedHandler = null;

//是否为多块请求,默认为否
   boolean multipartRequestParsed = false;


//获取与当前请求关联的异步管理器,用于执行异步操作
   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {

//用于保存处理适配器执行处理器后的返回结果
      ModelAndView mv = null;

//用于保存处理过程中发生的异常
      Exception dispatchException = null;

      try {

//1.检查多块请求,如果是多块请求,则返回一个新的请求,processedRequest保存这个新的请求引用,否则返回原始请求,即如果是多块请求,这个request会改变为其他类型的request,这时候赋值给processedRequest ,那么processedRequest 与doDispatch的参数request就不是同一个类型了,则下面判断就会为true
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // Determine handler for the current request.

//2.获取可处理当前请求的请求处理器,通过HanlderMapping查找,请求处理器中封装了拦截器链和对应的处理器,可以是具体的处理器方法
         mappedHandler = getHandler(processedRequest);

//如果没有,则执行没有处理器的逻辑
         if (mappedHandler == null) {

//内部逻辑判断配置this.throwExceptionIfNoHandlerFound是否为true,如果为true则抛出异常,否则直接设置响应内容为404;可以通过spring.mvc.throwExceptionIfNoHandlerFound设置值,默认为false
            noHandlerFound(processedRequest, response);
            return;
         }
//3.根据当前请求的处理器获取支持该处理器的处理适配器
         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.

//4.单独处理last-modified请求头,用于判断请求内容是否修改,如果未修改直接返回,浏览器使用本地缓存\
         String method = request.getMethod();
         boolean isGet = HttpMethod.GET.matches(method);

//只有get和head请求执行该判断
         if (isGet || HttpMethod.HEAD.matches(method)) {

//具体实现还是通过处理适配器来实现的;

//通过处理适配器的getListModified方法,传入请求与处理器,获取该请求对应的内容最后修改时间;

//一般针对静态资源,返回静态资源的上一次修改时间,动态资源固定返回-1,表示不存在该时间;

//具体看一下这位大佬写的https://blog.csdn.net/iwts_24/article/details/84575045
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());

//经过判断,如果最后修改时间在当前请求中浏览器缓存时间之前,则直接返回状态码304,表示未修改,浏览器可直接使用本地缓存作为请求内容;
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }
//5.通过mapperdHandler这个HanlderExecutionChain执行链的封装,链式执行其中所有拦截器的前置拦截方法preHandle
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }
//6.最终执行处理器适配器的处理方法,传入请求,响应与其对应的处理器,对请求进行处理。在这个处理中,最终调用到了请求对应的处理器方法;

//执行的返回值是ModelAndView类型,封装了模型数据与视图;
         // Actually invoke the handler.
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//如果异步处理开始,则直接返回,后续处理均通过异步执行;

//https://blog.csdn.net/woshilijiuyi/article/details/79316105
         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }
//7.应用默认视图名,如果返回值的ModelAndView中不包含视图名,则根据请求设置默认视图名,具体逻辑后面说明;
         applyDefaultViewName(processedRequest, mv);

//8.请求处理正常完成,链式执行所有拦截器的postHandle方法。链式顺序与preHandle相反
         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.

//在springmvc4.3版本之后,添加了支持Error类型异常的处理。Throwable的子类除了Exception就是Error

//可以通过@ExceptionHandler处理这种类型的异常

//封装为嵌套异常以供异常处理逻辑使用
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }

//9.对上面逻辑的执行结果进行处理,包括处理适配器的执行结果处理以及发生异常处理等逻辑;
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {

//10.拦截后链式执行拦截器链的afterCompletion方法。在该方法内部判断mappedHandler是否为空,如果不为空,则执行triggerAfterCompletion方法
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {

//11.拦截Error类型异常,拦截后链式执行拦截器链的afterCompletion方法
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {

//12.finally块,做资源清理
      if (asyncManager.isConcurrentHandlingStarted()) {

//如果在异步请求执行中,则链式执行拦截器链中的atferConcurrentHandlingStarted方法,即针对异步请求的特殊处理
         // 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);
         }
      }
   }
}

什么是多块请求:
processedRequest = checkMultipart(request);
在springmvc中,对于多块请求有特殊处理,如@RequestPart绑定多块请求参数与多块请求文件等;要想实现这些特殊处理,就需要先对请求类型为多块的请求执行预处理,在请求分发前就执行该操作,并替换后续使用的请求已经预处理过的多块请求;

上面的checkMultipart方法,就是判断是否为多块请求,使用了多块解析器StandarServletMultipartResolver进行解析,如果该request的content-Type为multipart/开头的话即为多块请求;

查找请求处理器:

mappedHandler = getHandler(processedRequest);

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;
}

这里仅仅是遍历所有的处理器映射,以此尝试获取与当前请求匹配的处理器执行链,至于这个HandlerMapping是什么时候装载完所有的处理器对应关系的,后边说;

获取处理适配器:

HandlerAdapter 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 new ServletException("No adapter for handler [" + handler +
         "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

Springmvc对于请求处理器的查找与执行是分离的,而根据处理器的类型不同,又需要使用不同的适配器去执行该处理器,这正式HandlerAdapter的作用;

对于@RequestMapping注解注册的处理器,类型为HandlerMethod,通过RequestMappinHandlerMapping返回。该适配器为RequestMappingHandlerAdapter;

执行前置拦截器链:
mappedHandler.applyPreHandle(processedRequest, response)
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
   for (int i = 0; i < this.interceptorList.size(); i++) {
      HandlerInterceptor interceptor = this.interceptorList.get(i);
      if (!interceptor.preHandle(request, response, this.handler)) {
         triggerAfterCompletion(request, response, null);
         return false;
      }
      this.interceptorIndex = i;
   }
   return true;
}

执行拦截器的preHandle方法,任意一个拦截器如果返回的是false,则直接停止执行,视为处理完成,触发拦截器的完成后方法triggerAfterCompletion;

至于这个拦截器是什么时候和Handler绑定在一块的,就要看这个mappedHanlder了,其实里边就是封装了handler和它能执行的拦截器;

注意:this.interceptorIndex = i;的作用就是在调用triggerAfterCompletion方法时,只会调用那些已经执行了preHandle方法的拦截器的afterCompletion方法(除了当前preHandle方法返回false的拦截器外);

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
   for (int i = this.interceptorIndex; i >= 0; i--) {
      HandlerInterceptor interceptor = this.interceptorList.get(i);
      try {
         interceptor.afterCompletion(request, response, this.handler, ex);
      }
      catch (Throwable ex2) {
         logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
      }
   }
}

可以看到在for循环中,int i = this.interceptorIndex

处理适配器执行:ha就是前面的适配器,mappedHandler里封装了handler和拦截器

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

这个是整个请求处理逻辑最核心的地方,参数的解析和返回值的解析Resolver都是在这里边进行处理;比如说参数解析@RequestParam,@RequestBody之类的,返回值解析@ResponseBody,视图等;

返回值视图名处理:
applyDefaultViewName(processedRequest, mv);
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
   if (mv != null && !mv.hasView()) {
      String defaultViewName = getDefaultViewName(request);
      if (defaultViewName != null) {
         mv.setViewName(defaultViewName);
      }
   }
}

为什么要存在这个默认视图名填充呢?

因为有的处理器会使用void,或者返回null,在没有@ResponseBody的情况下,默认是会需要视图去返回给前端的,如果这时候返回null的话,则默认设置请求路径的名字去查找视图,如果没找到的话,就会报错;与这里面的mv = ha.handle(processedRequest, response, mappedHandler.getHandler());一个属性有关系

mavContainer.setRequestHandled(false);如果它为false的话,那么默认是会实例化一个ModelAndView的,那么这个时候回到上面的applyDefaultViewName(processedRequest, mv);,这时候mv是不为null的就会设置默认的视图名;具体可以看下别人写的blog.csdn.net/QAQFyl/arti…


执行后置拦截器链:
mappedHandler.applyPostHandle(processedRequest, response, mv);
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
      throws Exception {

   for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
      HandlerInterceptor interceptor = this.interceptorList.get(i);
      interceptor.postHandle(request, response, this.handler, mv);
   }
}
这里是倒序,那么拦截器的prehandle执行顺序就会是A -> B -> C,postHandle就是C -> B -> A;
处理返回值与响应:

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
      @Nullable Exception exception) throws Exception {
//标记是否是error视图
   boolean errorView = false;
//如果出现了异常
   if (exception != null) {

//如果异常类型为ModelAndViewDefiningException
      if (exception instanceof ModelAndViewDefiningException) {

//该异常内部包含一个ModelAndView类型的属性,用于提供包含ModelAndView结果的异常封装
         logger.debug("ModelAndViewDefiningException encountered", exception);

//直接使用异常中封装的ModelAndView作为最终的ModelAndView结果;
         mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else {

//其他异常类型,先获取处理器
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);

//执行process处理器异常方法,获取处理了异常结果后得到的ModelAndView结果
         mv = processHandlerException(request, response, handler, exception);

//如果mv不为空,则说明返回了包含异常的视图,即返回的视图为异常视图;
         errorView = (mv != null);
      }
   }

   // Did the handler return a view to render?

//如果视图与模型不为空,且视图与模型没有标记为被清理(被清理表示调用过ModelAndView的clear方法,清理后的ModelAndView相当于null)
   if (mv != null && !mv.wasCleared()) {

//视图与模型不为空时,执行渲染视图的操作
      render(mv, request, response);

//如果时异常视图,渲染后需要清空请求属性中的异常信息\
      if (errorView) {
         WebUtils.clearErrorRequestAttributes(request);
      }
   }
   else {
      if (logger.isTraceEnabled()) {

//如果视图为null,则打印一个日志
         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);
   }
}

在无异常的情况下,主要处理对象是处理适配器返回的ModelAndView。将它render出去;
如果存在异常了,异常处理后形成ModelAndView,使用这个ModelAndView继续执行后续处理;
其中mv = processHandlerException(request, response, handler, exception);是@ExceptionHandler的处理地方:
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) {

//执行处理器异常解析器的解析异常方法,拿到解析的ModelAndView结果
         exMv = resolver.resolveException(request, response, handler, ex);

//如果不为空,则将此结果作为对异常处理后的ModelAndView结果使用,中断后续的遍历动作
         if (exMv != null) {
            break;
         }
      }
   }

//如果返回的异常ModelAndView不为null
   if (exMv != null) {

//如果ModelAndView内部为空(View为空且Model为空)
      if (exMv.isEmpty()) {
         request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
         return null;
      }
      // We might still need view name translation for a plain error model...

//如果异常ModelAndView 不包含视图,但是包含Model不为空
      if (!exMv.hasView()) {

//采用与doDispatch方法中相同的处理逻辑来根据请求获取默认视图名
         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;
   }
//如果没有处理器异常解析器,exMv为null,则原封不懂抛出原始异常,交给web框架处理
   throw ex;
}

然后回到processDispatchResult方法,mv = processHandlerException(request, response, handler, exception);
这时候mv一定不为null,如果为null,在里边就抛错了;

另一个比较重要的方法就是render(mv, request, response);到这里,ModelAndView是一定不为null的;

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   // Determine locale for request and apply it to the response.

//先通过Locale解析器获取请求对应的Locale
   Locale locale =
         (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());

//设置获取的Locale为相应的Locale
   response.setLocale(locale);
//最终获取的视图
   View view;

//如果ModelAndView中的视图为视图名,则获取这个视图名
   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 {

//到这,view肯定不为null

//如果ModelAndView中的status部位为空,则把其设置为相应的状态码,@ResponseStatus设置的状态码功能就是通过这里实现的
      if (mv.getStatus() != null) {
         response.setStatus(mv.getStatus().value());
      }

//执行视图的渲染方法,每种模板引擎都有相应的视图实现,视图渲染对应于模板引擎的渲染模板,fremarker,thymeleaf这些,还有默认的jsp -> AbstractView -> InternalResourceView类;
      view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
      if (logger.isDebugEnabled()) {
         logger.debug("Error rendering view [" + view + "]", ex);
      }
      throw ex;
   }
}
视图名解析器:
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;
}