SpringMVC源码分析之视图解析器

因为视图解析器比较重要,所以单独开了一个随笔, 源码还是在doDispatch()方法中。在我之前的随笔中有纪录

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

  核心方法就是doDispatch() 中的 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 

  SpringMVC封装了一个 ModelAndViewContainer类,里面提供了 getModel()  getView()  getViewName() 等方法。

  并且在我们拿到ModelAndView之前,利用ModelFactory工厂初始化了ModelAndViewContainer 初始化方法如下

public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
            throws Exception {

        Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
        container.mergeAttributes(sessionAttributes);
        invokeModelAttributeMethods(request, container);

        for (String name : findSessionAttributeArguments(handlerMethod)) {
            if (!container.containsAttribute(name)) {
                Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
                if (value == null) {
                    throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
                }
                container.addAttribute(name, value);
            }
        }
    }

  所以最后我们可以直接从ModelAndView容器中取得ModelAndView对象返回

2、视图渲染流程:将域中的数据在页面展示;页面就是来渲染模型数据的

  这个过程调用的是doDispatch()方法中的  processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 方法

  这个方法的关键代码是

// 刚刚我们已经拿到了ModelAndView对象并赋值给了mv 所以这里mv不为空
        if (mv != null && !mv.wasCleared()) {
       // 进入render方法 渲染数据模型 render(mv, request, response);
if (errorView) { WebUtils.clearErrorRequestAttributes(request); } }

  进入render方法

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;
     // 方法返回值,路径名 就是我们在Controller写的方法,最后return的String String viewName
= mv.getViewName(); if (viewName != null) { // viewResolver的作用是根据视图名(方法的返回值)得到view对象 ViewResolver是一个接口,里面只声明了一个resolveViewName方法 . 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; } }

  1)、接下来先看看 ViewResolver(视图解析器) 是怎么解析的 即 resolveViewName()方法 

  说明:这个VIewResolver有很多实现,其中一个就是我们常用在spring-servlet.xml配置的 InternalResourceViewResolver 

@Nullable
    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
            Locale locale, HttpServletRequest request) throws Exception {

        if (this.viewResolvers != null) {
        // 这里遍历所有的ViewResolver,就会拿到上述InternalResourceViewResolver实现 因为我没在配置文件中配了,如果没有配,就用默认的
for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } return null; }
resolveViewName一个核心方法就是 createView(viewName, locale);
解释:
InternalResourceViewResolver 继承了  UrlBasedViewResolver 继承了  AbstractCachingViewResolver 声明了  createView() 方法 
   
UrlBasedViewResolver 重写了  createView() 方法。 代码如下 重写这个方法的作用就是为了处理转发和重定向的请求
@Override
    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;
        }

        // Check for special "redirect:" prefix. 如果是redirect:前缀,就执行重定向
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            RedirectView view = new RedirectView(redirectUrl,
                    isRedirectContextRelative(), isRedirectHttp10Compatible());
            String[] hosts = getRedirectHosts();
            if (hosts != null) {
                view.setHosts(hosts);
            }
            return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
        }

        // Check for special "forward:" prefix. 如果是forward:前缀,就执行转发
        if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            InternalResourceView view = new InternalResourceView(forwardUrl);
            return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
        }

        // 如果没有前缀,就默认使用父类创建一个View对象
        return super.createView(viewName, locale);
    }
  源码看到这里,大概就知道View是怎么创建的了。
  小结:
    视图解析器得到view对象的流程就是,所有配置的视图解析器都来尝试根据视图名(就是自己自定义Controller类下个各个方法的返回值,String路径名)得到VIew(视图)对象
  如果能得到就返回,得不到就换下一个视图解析器

   2)、SpringMVC拿到了View对象后又做了什么?

      调用render()方法,代码如下

@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<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);

        // Expose helpers as request attributes, if any.
        exposeHelpers(request);

        // Determine the path for the request dispatcher.
        String dispatcherPath = prepareForRendering(request, response);

        // Obtain a RequestDispatcher for the target resource (typically a JSP).
        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() + "]");
            }
        // Servlet 原生的转发请求 rd.forward(request, response); } }
 

总结:

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

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

 

猜你喜欢

转载自www.cnblogs.com/lxy-java/p/12910751.html
今日推荐