SpringMVC源码阅读ViewResolver如何处理ContentNegotiatingViewResolver(九)

接口

   public interface ViewResolver {
        /**
         * 通过viewName和locale查找View
         * @param viewName
         * @param locale
         * @return
         * @throws Exception
         */
        @Nullable
        View resolveViewName(String viewName, Locale locale) throws Exception;
    }

类图

 

 spring boot 默认用了以上几种

ContentNegotiatingViewResolver 为排列的第一个 他内部什么都不做只是维护了多个Resolver加了排序  所以我们通过这个为入口开始看源码

ContentNegotiatingViewResolver

initServletContext

org.springframework.web.servlet.view.ContentNegotiatingViewResolver#initServletContext

 

    protected void initServletContext(ServletContext servletContext) {
        /**
         * 从容器中获得所有ViewResolver  我们可以自定义我们的ViewResolver
         */
        Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
        ViewResolver viewResolver;
        if (this.viewResolvers == null) {
            this.viewResolvers = new ArrayList(matchingBeans.size());
            Iterator var3 = matchingBeans.iterator();

            while(var3.hasNext()) {
                viewResolver = (ViewResolver)var3.next();
                //排除当前对象 因为当前resolver也在容乃公器里面
                if (this != viewResolver) {
                    this.viewResolvers.add(viewResolver);
                }
            }
        } else {
            for(int i = 0; i < this.viewResolvers.size(); ++i) {
                viewResolver = (ViewResolver)this.viewResolvers.get(i);
                if (!matchingBeans.contains(viewResolver)) {
                    String name = viewResolver.getClass().getName() + i;
                    this.obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolver, name);
                }
            }
        }
        //根据@Order注解进行优先级排序
        AnnotationAwareOrderComparator.sort(this.viewResolvers);
        this.cnmFactoryBean.setServletContext(servletContext);
    }

 

DispatcherServlet

org.springframework.web.servlet.DispatcherServlet#doDispatch

->

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

->

org.springframework.web.servlet.DispatcherServlet#render

render

 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //调用localeResolver
        Locale locale = this.localeResolver.resolveLocale(request);
        response.setLocale(locale);
        View view;
        if (mv.isReference()) {
            //<1>根据viewResolver获得view
            view = this.resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
            }
        } else {
            //如果我们ModelAndView直接返回的View则直接获取Vew
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + this.getServletName() + "'");
            }
        }

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'");
        }

        try {
            //解析
            view.render(mv.getModelInternal(), request, response);
        } catch (Exception var7) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var7);
            }

            throw var7;
        }
    }

<1>resolveViewName

org.springframework.web.servlet.DispatcherServlet#doDispatch

->

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

->

org.springframework.web.servlet.DispatcherServlet#render

->

org.springframework.web.servlet.DispatcherServlet#resolveViewName

 

    protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
        //viewResolvers dispatcher init初始化 第一个为ContentNegotiatingViewResolver
        Iterator var5 = this.viewResolvers.iterator();
        View view;
        do {
            if (!var5.hasNext()) {
                return null;
            }
            ViewResolver viewResolver = (ViewResolver)var5.next();
            //调用<2>viewResolver 传入viewName和locale 获得View
            view = viewResolver.resolveViewName(viewName, locale);
        } while(view == null);

        return view;
    }

 

ContentNegotiatingViewResolver

org.springframework.web.servlet.DispatcherServlet#doDispatch

->

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

->

org.springframework.web.servlet.DispatcherServlet#render

->

org.springframework.web.servlet.DispatcherServlet#resolveViewName

->

org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName

<2>resolveViewName

public View resolveViewName(String viewName, Locale locale) throws Exception {
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
        /**
         * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,q=0.8,application/signed-exchange;v=b3
         * 获得request head里面的Accept值  ,号分割
         * 此为http协议里面代表客户端期望接收的数据类型
         */
        List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
        if (requestedMediaTypes != null) {
            /**
             * <3>遍历所有ViewResolve 获得view 注意:这里可能获取到多个
             */
            List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
            /**
             *<5>根据requestedMediaTypes 和attrs决策出一个最优的view
             */
            View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
            if (bestView != null) {
                return bestView;
            }
        }

        String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";
        if (this.useNotAcceptableStatusCode) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
            }
            //返回406的view
            /**
             * private static final View NOT_ACCEPTABLE_VIEW = new View() {
             *         @Nullable
             *         public String getContentType() {
             *             return null;
             *         }
             *
             *         public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
             *             response.setStatus(406);
             *         }
             *     };
             */
            return NOT_ACCEPTABLE_VIEW;
        } else {
            this.logger.debug("View remains unresolved" + mediaTypeInfo);
            return null;
        }
    }

<3>getCandidateViews

org.springframework.web.servlet.DispatcherServlet#doDispatch

->

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

->

org.springframework.web.servlet.DispatcherServlet#render

->

org.springframework.web.servlet.DispatcherServlet#resolveViewName

->

org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName

->

org.springframework.web.servlet.view.ContentNegotiatingViewResolver#getCandidateViews

    private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
        List<View> candidateViews = new ArrayList();
        if (this.viewResolvers != null) {
            Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
            Iterator var5 = this.viewResolvers.iterator();

            while(var5.hasNext()) {
                ViewResolver viewResolver = (ViewResolver)var5.next();
                //遍历resolver选择合适的view
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    candidateViews.add(view);
                }

                Iterator var8 = requestedMediaTypes.iterator();

                while(var8.hasNext()) {
                    MediaType requestedMediaType = (MediaType)var8.next();
                    /**
                     * <4>这里主要根据客户端期待的数据类型 获得后缀 再次调用resolve生成view默认都是获取空
                     * 比如我们viewname是index  inde.jsp  index.html  index.xml
                     *   private final Set<MediaTypeFileExtensionResolver> resolvers; 默认为空 我们有需求可以给成员变量注入映射关系
                     */
                    List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
                    Iterator var11 = extensions.iterator();

                    while(var11.hasNext()) {
                        String extension = (String)var11.next();
                        //拼接后缀生成view
                        String viewNameWithExtension = viewName + '.' + extension;
                        view = viewResolver.resolveViewName(viewNameWithExtension, locale);
                        if (view != null) {
                            candidateViews.add(view);
                        }
                    }
                }
            }
        }
        //是否有配置默认view 如果有配置将默认views加入进去 。比如404  html的view ajax的view
        if (!CollectionUtils.isEmpty(this.defaultViews)) {
            candidateViews.addAll(this.defaultViews);
        }

        return candidateViews;
    }
}

<4>resolveFileExtensions

org.springframework.web.servlet.DispatcherServlet#doDispatch

->

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

->

org.springframework.web.servlet.DispatcherServlet#render

->

org.springframework.web.servlet.DispatcherServlet#resolveViewName

->

org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName

->

org.springframework.web.servlet.view.ContentNegotiatingViewResolver#getCandidateViews

->

org.springframework.web.accept.ContentNegotiationManager#resolveFileExtensions

 

 public List<String> resolveFileExtensions(MediaType mediaType) {
        Set<String> result = new LinkedHashSet();
        //默认为空 获得resolvers
        Iterator var3 = this.resolvers.iterator();

        while(var3.hasNext()) {
            MediaTypeFileExtensionResolver resolver = (MediaTypeFileExtensionResolver)var3.next();
            //映射关系转换
            result.addAll(resolver.resolveFileExtensions(mediaType));
        }

        return new ArrayList(result);
    }

<5>candidateViews

    @Nullable
    private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs) {
        Iterator var4 = candidateViews.iterator();

        while(var4.hasNext()) {
            //如果是重定向view 直接返回
            View candidateView = (View)var4.next();
            if (candidateView instanceof SmartView) {
                SmartView smartView = (SmartView)candidateView;
                if (smartView.isRedirectView()) {
                    return candidateView;
                }
            }
        }

        var4 = requestedMediaTypes.iterator();

        while(var4.hasNext()) {
            MediaType mediaType = (MediaType)var4.next();
            Iterator var10 = candidateViews.iterator();

            while(var10.hasNext()) {
                View candidateView = (View)var10.next();
                if (StringUtils.hasText(candidateView.getContentType())) {
                    //获得view 的contentType 并做叛逆的是否符合
                    MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType());
                    if (mediaType.isCompatibleWith(candidateContentType)) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Selected '" + mediaType + "' given " + requestedMediaTypes);
                        }

                        attrs.setAttribute(View.SELECTED_CONTENT_TYPE, mediaType, 0);
                        return candidateView;
                    }
                }
            }
        }

        return null;
    }

总结 

1.ContentNegotiatingViewResolver并没有做View的查找工作 只是内部继承了其他Resolver并支持Order排序

2.同时提供支持通过MetaInfo从多个适配View适配最合适的view 因为dispacher适配一个就立即返回

3.我们可以直接返回view对象 如果返回一个字符串将通过ViewResolver做适配查找对应的View

 

 

 
posted @ 2020-01-22 14:21  意犹未尽  阅读(343)  评论(0编辑  收藏  举报