SpringMVC是怎样根据方法执行后返回值一步步到达页面的?

SpringMVC视图解析

  • 方法执行后的返回值会作为页面地址参考,转发或者重定向到页面
  • 视图解析器可能会进行页面地址的拼串

任何方法的返回值最终都会被包装成ModelAndView对象

//DisoatcherServlet来到页面的方法
//mv就是上面目标方法执行完后的返回值,被封装成ModelAndView(内有视图名及隐含模型中的数据)
//其它参数就是我的请求相应,请求对应的处理器(链)以及调度期间发生的错误对象
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

来进一步看一下processDispatchResult方法

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
      @Nullable Exception exception) throws Exception {

   boolean errorView = false;
   //如果有错误的时候(即传入的Exception不为空) 怎样处理
   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);
      }
   }


   //ModelAndView不等于空并且没有被清理掉 
   if (mv != null && !mv.wasCleared()) {
       //渲染页面(request与response用来转发或者重定向,mv有你要去往的页面参考和模型数据)
      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);
   }
}

render方法

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   //国际化有关
   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;
   }
}

ViewResolver的作用:根据视图名(方法返回值)得到View对象

其接口只有一个方法,作用就是根据视图名返回视图对象

View resolveViewName(String viewName, Locale locale) throws Exception;
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 view = viewResolver.resolveViewName(viewName, locale);
         if (view != null) {
            return view;
         }
      }
   }
   return null;
}

AbstractCachingViewResovler中 resolveViewName实现

public View resolveViewName(String viewName, Locale locale) throws Exception {
   if (!isCache()) {
      return createView(viewName, locale);
   }
   else {
      Object cacheKey = getCacheKey(viewName, locale);
      View view = this.viewAccessCache.get(cacheKey);
      if (view == null) {
         synchronized (this.viewCreationCache) {
            view = this.viewCreationCache.get(cacheKey);
            if (view == null) {
               //根据方法的返回值创建出视图对象
               view = createView(viewName, locale);
               if (view == null && this.cacheUnresolved) {
                  view = UNRESOLVED_VIEW;
               }
               //创建完View对象还给缓存put进去了,下次如果一样就直接从缓存拿
               if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
                  this.viewAccessCache.put(cacheKey, view);
                  this.viewCreationCache.put(cacheKey, view);
               }
            }
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace(formatKey(cacheKey) + "served from cache");
         }
      }
      return (view != UNRESOLVED_VIEW ? view : null);
   }
}

createView方法(创建View对象)

protected View createView(String viewName, Locale locale) throws Exception {
   // If this resolver is not supposed to handle the given view,
   // return null to pass on to the next resolver in the chain.
   //判断一下能否进行处理
   if (!canHandle(viewName, locale)) {
      return null;
   }

   //检查有没有特殊前缀redirect:
   if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
      String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
      //返回一个RedirectView
      RedirectView view = new RedirectView(redirectUrl,
            isRedirectContextRelative(), isRedirectHttp10Compatible());
      String[] hosts = getRedirectHosts();
      if (hosts != null) {
         view.setHosts(hosts);
      }
      return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
   }

   //检查有没有特殊前缀forward:
   if (viewName.startsWith(FORWARD_URL_PREFIX)) {
      String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
      //返回一个InternalResourceView
      InternalResourceView view = new InternalResourceView(forwardUrl);
      return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
   }

   //没前缀使用父类默认createView方法给你创建一个View对象(InternalResourceView)(拼串)
   return super.createView(viewName, locale);
}

返回View对象

视图解析器得到对象的流程就是:所有配置好的视图解析器都来尝试根据视图名(返回值)得到View对象

若能得到就返回View对象,如果得不到就换下一个视图解析器

得到View后调用View对象的render方法渲染页面

//第一个参数就是ModelAndView中隐含模型中你放的数据
view.render(mv.getModelInternal(), request, response);

来到具体View的实现类的render方法(AbstractView类)

@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
      HttpServletResponse response) throws Exception {

   if (logger.isDebugEnabled()) {
      logger.debug("View " + formatViewName() +
            ", model " + (model != null ? model : Collections.emptyMap()) +
            (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
   }
   //创建一个合并的模型输出(数据Map)
   Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
   //准备相应
   prepareResponse(request, response);
    
   //渲染要给页面输出的所有数据
   renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

InternalResourceView的 renderMergedOutputModel方法

@Override
protected void renderMergedOutputModel(
      Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

   //将隐含 模型中的数据加入到请求域中
   exposeModelAsRequestAttributes(model, request);

   //请求要爆露在转发中
   exposeHelpers(request);

   //拿到最终要去的资源的路径
   String dispatcherPath = prepareForRendering(request, response);

   //调用Servlet原生的API拿到请求转发器
   RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
   if (rd == null) {
      throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
            "]: Check that the corresponding file exists within your web application archive!");
   }

   // If already included or response already committed, perform include, else forward.
   if (useInclude(request, response)) {
      response.setContentType(getContentType());
      if (logger.isDebugEnabled()) {
         logger.debug("Including [" + getUrl() + "]");
      }
      rd.include(request, response);
   }

   else {
      // Note: The forwarded resource is supposed to determine the content type itself.
      if (logger.isDebugEnabled()) {
         logger.debug("Forwarding to [" + getUrl() + "]");
      }
      //真正转发
      rd.forward(request, response);
   }
}

将模型中的数据加入到请求域中

protected void exposeModelAsRequestAttributes(Map<String, Object> model,
      HttpServletRequest request) throws Exception {
   //遍历map中的所有key,value放到请求域中
   model.forEach((name, value) -> {
      if (value != null) {
         request.setAttribute(name, value);
      }
      else {
         request.removeAttribute(name);
      }
   });
}

总结:视图解析器只是为了得到视图对象,视图对象才能真正的转发(将模型中数据放到请求域中)或者重定向到页面

视图对象才能真正的渲染视图

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页