• 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

     
  • 相关阅读:
    Cannot set property 'branchdata' of undefined
    关闭Vue Eslint语法检查
    Webpack前世今生
    SpringCloud 之 Netflix Hystrix 服务监控
    Spring Cloud 之 Netflix Hystrix 服务容错
    Java设计模式之建造者模式(Builder Pattern)
    Java设计模式之工厂模式(Factory Pattern)
    数据库表的字段有默认值,如何修改或者去掉这个默认值
    前端基础进阶(十五):详解 ES6 Modules
    JS基础知识题(中)作用域、闭包
  • 原文地址:https://www.cnblogs.com/LQBlog/p/12228515.html
Copyright © 2020-2023  润新知