zoukankan      html  css  js  c++  java
  • SpringMVC的视图解析器

    视图和视图解析器

    • 请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图

    • Spring MVC 借助视图解析器ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是 Excel、JFreeChart等各种表现形式的视图

    • 对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦

    视图

    视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口:

    image-20210407232126418

    视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题

    常用的视图实现类

    image-20210407232201025

    视图解析器

    SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。

    视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。

    所有的视图解析器都必须实现 ViewResolver 接口:

    image-20210407232255268

    程序员可以选择一种视图解析器或混用多种视图解析器。每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序order 越小优先级越高。 SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出 ServletException 异常。

    JSP 是最常见的视图技术,可以使用 InternalResourceViewResolve作为视图解析器:

    image-20210407232324201

    实验代码

    请求转发

     @RequestMapping("/hello1")
        public String hello1(){
            return "../../hello";
        }
    
        @RequestMapping("/hello2")
        public String hello2(){
            return "forward:/hello.jsp";
        }
    
        @RequestMapping("/hello3")
        public String hello3(){
            return "forward:/hello2";
        }
    
        @RequestMapping("/hello4")
        public String hello4(){
            return "redirect:/index.jsp";
        }
    

    都可以跳到index.jsp

    源码断点

    1.任何的方法都会进入到doDispatch(request, response);中

    2.在它的方法中mv = ha.handle(processedRequest, response, mappedHandler.getHandler());,有处理器适配器调用生成ModelAndView

    3.调用handle方法,是由子类AnnotationMethodHandlerAdapter实现的,里面会调用invokeHandlerMethod(request, response, handler)方法。

    image-20210407210635920

    image-20210407210732119

    最终会返回ModelAndView对象,view就是要跳转的页面,model里面就是数据。

    4.方法继续放下走到processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    			HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
    
    		boolean errorView = false;
    
    		if (exception != null) {
    			if (exception instanceof ModelAndViewDefiningException) {
    				logger.debug("ModelAndViewDefiningException encountered", exception);
    				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
    			}
    			else {
    				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
    				mv = processHandlerException(request, response, handler, exception);
    				errorView = (mv != null);
    			}
    		}
    
    		// Did the handler return a view to render?
    		if (mv != null && !mv.wasCleared()) {
                //要进行渲染
    			render(mv, request, response);
    			if (errorView) {
    				WebUtils.clearErrorRequestAttributes(request);
    			}
    		}
    		else {
    			if (logger.isDebugEnabled()) {
    				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
    						"': assuming HandlerAdapter completed request handling");
    			}
    		}
    
    		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    			// Concurrent handling started during a forward
    			return;
    		}
    
    		if (mappedHandler != null) {
    			mappedHandler.triggerAfterCompletion(request, response, null);
    		}
    	}
    

    5.render(mv, request, response);

    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.resolveLocale(request);
    		response.setLocale(locale);
    
    		View view;
    		if (mv.isReference()) {
    			// We need to resolve the view name.
    			view = 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 '" +
    								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.isDebugEnabled()) {
    			logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
    		}
    		try {
    			view.render(mv.getModelInternal(), request, response);
    		}
    		catch (Exception ex) {
    			if (logger.isDebugEnabled()) {
    				logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '"
    						+ getServletName() + "'", ex);
    			}
    			throw ex;
    		}
    	}
    

    其中有一个重要的方法resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);,解析试图名,点进去看看。

    image-20210407211548246

    试图解析器根据方法的返回值,得到一个view对象。要是不为空,说明能解析。

    @Override
    	public View resolveViewName(String viewName, Locale locale) throws Exception {
    		if (!isCache()) {
    			return createView(viewName, locale);
    		}
    		else {
    			Object cacheKey = getCacheKey(viewName, locale);
    			View view = this.viewAccessCache.get(cacheKey);
    			if (view == null) {
    				synchronized (this.viewCreationCache) {
    					view = this.viewCreationCache.get(cacheKey);
    					if (view == null) {
    						// Ask the subclass to create the View object.
                            //根据方法的返回值创建view对象
    						view = createView(viewName, locale);
    						if (view == null && this.cacheUnresolved) {
    							view = UNRESOLVED_VIEW;
    						}
    						if (view != null) {
    							this.viewAccessCache.put(cacheKey, view);
    							this.viewCreationCache.put(cacheKey, view);
    							if (logger.isTraceEnabled()) {
    								logger.trace("Cached view [" + cacheKey + "]");
    							}
    						}
    					}
    				}
    			}
    			return (view != UNRESOLVED_VIEW ? view : null);
    		}
    	}
    

    ​ view = createView(viewName, locale);这个方法,是真正的创建试图。

    image-20210407212210497

    创建完成,会有一个view

    image-20210407212622558

    视图解析器得到View对象的流程就是,所有配置的视图解析器都来尝试根据视图名(返回值)得到View(试图)对象;如果能得到就返回,得不到就换下一个试图解析器。

    6.view进行渲染。

    image-20210407234603590

    image-20210407234711644

    @Override
    	protected void renderMergedOutputModel(
    			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    		// Determine which request handle to expose to the RequestDispatcher.
    		HttpServletRequest requestToExpose = getRequestToExpose(request);
    
    		// Expose the model object as request attributes.
    		exposeModelAsRequestAttributes(model, requestToExpose);
    
    		// Expose helpers as request attributes, if any.
    		exposeHelpers(requestToExpose);
    
    		// Determine the path for the request dispatcher.
    		String dispatcherPath = prepareForRendering(requestToExpose, response);
    
    		// Obtain a RequestDispatcher for the target resource (typically a JSP).
    		RequestDispatcher rd = getRequestDispatcher(requestToExpose, 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(requestToExpose, response)) {
    			response.setContentType(getContentType());
    			if (logger.isDebugEnabled()) {
    				logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
    			}
    			rd.include(requestToExpose, response);
    		}
    
    		else {
    			// Note: The forwarded resource is supposed to determine the content type itself.
    			if (logger.isDebugEnabled()) {
    				logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
    			}
    			rd.forward(requestToExpose, response);
    		}
    	}
    

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

    拓展

      <!-- 直接配置响应的页面:无需经过控制器来执行结果 -->
          <mvc:view-controller path="/success" view-name="success"/>
    

    image-20210407235055483

    但是请求别的接口的时候,都报错404。

    image-20210407235118641

    配置mvc:view-controller会导致其他请求路径失效

    解决办法:

    <mvc:annotation-driven/>
    

    自定义视图和视图解析器

    /**
     * @author WGR
     * @create 2021/4/8 -- 0:06
     */
    @Component
    public class HelloView implements View {
    
        @Override
        public String getContentType() {
            return "text/html";
        }
    
        @Override
        public void render(Map<String, ?> model, HttpServletRequest request,
                           HttpServletResponse response) throws Exception {
            System.out.println(model);
            response.setContentType("text/html");
            response.getWriter().println("HelloView - time = " + new Date());
            response.getWriter().println(model.get("name"));
        }
    }
    
    /**
     * @author WGR
     * @create 2021/4/8 -- 0:04
     */
    @Component
    public class MyViewResolver implements ViewResolver, Ordered {
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
           if(viewName.startsWith("wgr")){
               return new HelloView();
           }else {
               return null;
           }
    
        }
    
        @Override
        public int getOrder() {
            return 1;
        }
    }
    
        @RequestMapping("/testView")
        public String testView(Model model){
            System.out.println("testView...");
            model.addAttribute("name","dalianpai");
            return "wgr:/helloView"; //与视图Bean 对象的id一致
        }
    

    image-20210408001505056

    image-20210408001535375

    image-20210408002404545

  • 相关阅读:
    SP338 ROADS
    [Usaco2008 Mar]牛跑步
    [Cerc2005]Knights of the Round Table
    [Poi2005]Piggy Banks小猪存钱罐
    Pku1236 Network of Schools
    PKU2186 Popular Cows 受欢迎的牛
    黑暗城堡
    入门OJ:最短路径树入门
    Sqli-labs
    Sqli-labs
  • 原文地址:https://www.cnblogs.com/dalianpai/p/14630333.html
Copyright © 2011-2022 走看看