zoukankan      html  css  js  c++  java
  • 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); } }
    
    

    总结:

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

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

     
  • 相关阅读:
    remove white space from read
    optimize the access speed of django website
    dowload image from requests
    run jupyter from command
    crawl wechat page
    python version 2.7 required which was not found in the registry windows 7
    health
    alternate rows shading using conditional formatting
    word
    【JAVA基础】static 关键字
  • 原文地址:https://www.cnblogs.com/lxy-java/p/12910751.html
Copyright © 2011-2022 走看看