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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM