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

    总结:

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

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

     
  • 相关阅读:
    大量建置账号
    MYSQL远程登录权限设置
    设置mysql远程连接root权限
    阿里云服务器上安装mysql的心路历程(博友们进来看看哦)
    Array.Copy
    C#如何判断两个数组相等
    CentOS6.5下编译安装mysql5.6.19
    Linux下卸载和安装MySQL[rpm包]
    查看Linux磁盘空间大小
    C# 数组CopyTo
  • 原文地址:https://www.cnblogs.com/lxy-java/p/12910751.html
Copyright © 2011-2022 走看看