視圖和視圖解析器 1.基本介紹 在SpringMVC中的目標方法,最終返回的都是一個視圖(有各種視圖) 註意,這裡的視圖是一個類對象,不是一個頁面!! 返回的視圖都會由一個視圖解析器來處理(視圖解析器有很多種) 2.自定義視圖 2.1為什麼需要自定義視圖 在預設情況下,我們都是返回預設的視圖,然後返 ...
視圖和視圖解析器
1.基本介紹
-
在SpringMVC中的目標方法,最終返回的都是一個視圖(有各種視圖)
註意,這裡的視圖是一個類對象,不是一個頁面!!
-
返回的視圖都會由一個視圖解析器來處理(視圖解析器有很多種)
2.自定義視圖
2.1為什麼需要自定義視圖
-
在預設情況下,我們都是返回預設的視圖,然後返回的視圖交由 SpringMVC 的
InternalResourcesViewResolver
預設視圖解析器來處理的: -
在實際開發中,因為業務需求,我們有時候需要自定義視圖解析器
-
視圖解析器可以配置多個,按照指定的順序來對視圖進行解析。如果上一個視圖解析器不匹配,下一個視圖解析器就會去解析視圖,以此類推。
2.2應用實例
執行流程:
-
view.jsp,請求到 Handler
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>自定義視圖測試</title> </head> <body> <h1>自定義視圖測試</h1> <a href="goods/buy">點擊到自定義視圖</a> </body> </html>
-
GoodsHandler.java
package com.li.web.viewresolver; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @author 李 * @version 1.0 */ @RequestMapping(value = "/goods") @Controller public class GoodsHandler { @RequestMapping(value = "/buy") public String buy(){ System.out.println("----------buy()---------"); return "liView";//自定義視圖名 } }
-
自定義視圖 MyView.java
package com.li.web.viewresolver; import org.springframework.stereotype.Component; import org.springframework.web.servlet.view.AbstractView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Map; /** * @author 李 * @version 1.0 * 1. MyView 繼承了AbstractView,就可以作為了一個視圖使用 * 2. @Component(value = "myView") ,該視圖會註入到容器中,id為 liView */ @Component(value = "liView") public class MyView extends AbstractView { @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //1.完成視圖渲染 System.out.println("進入到自己的視圖..."); //2.並且確定我們要跳轉的頁面,如 /WEB-INF/pages/my_view.jsp /* * 1.下麵就是請求轉發到 /WEB-INF/pages/my_view.jsp * 2.該路徑會被springmvc解析成 /web工程路徑/WEB-INF/pages/my_view.jsp */ request.getRequestDispatcher("/WEB-INF/pages/my_view.jsp") .forward(request, response); } }
-
結果頁面 my_view.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>my_view</title> </head> <body> <h1>進入到my_view頁面</h1> <p>從自定義視圖來的...</p> </body> </html>
-
springDispatcherServlet-servlet.xml 配置自定義視圖解析器
<!--1.指定掃描的包--> <context:component-scan base-package="com.li.web"/> <!--2.配置視圖解析器[預設的視圖解析器]--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--配置屬性 suffix(尾碼) 和 prefix(首碼)--> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean> <!--3. 3.1 配置自定義視圖解析器 BeanNameViewResolver 3.2 BeanNameViewResolver 可以解析我們自定義的視圖 3.3 屬性 order 表示視圖節解析器執行的順序,值越小優先順序越高 3.4 order 的預設值為最低優先順序-LOWEST_PRECEDENCE 3.5 預設的視圖解析器就是最低優先順序,因此我們的自定義解析器會先執行 --> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> <property name="order" value="99"/> </bean> <!--4.加入兩個常規的配置--> <!--支持SpringMVC的高級功能,比如:JSR303校驗,映射動態請求--> <mvc:annotation-driven></mvc:annotation-driven> <!--將SpringMVC不能處理的請求,交給tomcat處理,比如css,js--> <mvc:default-servlet-handler/>
-
測試,訪問view.jsp,點擊超鏈接
-
成功跳轉到 my_view.jsp
-
後臺輸出如下:說明整個執行流程如圖所示。
2.3創建自定義視圖的步驟
- 自定義一個視圖:創建一個 View 的 bean,該 bean 需要繼承自 AbstractView,並實現renderMergedOutputModel方法
- 並把自定義 View 加入到 IOC 容器中
- 自定義視圖的視圖解析器,使用 BeanNameViewResolver,這個視圖解析器也需要配置到 ioc 容器文件中
- BeanNameViewResolver 的調用優先順序需要設置一下,設置 order 比 Integer.MAX_VALUE 小的值,以確保其在預設的視圖解析器之前被調用
2.4Debug源碼-自定義視圖解析器執行流程
自定義視圖-工作流程:
- SpringMVC 調用目標方法,返回自定義 View 在 IOC 容器中的 id
- SpringMVC 調用 BeanNameViewResolver 視圖解析器:從 IOC 容器中獲取返回 id 值對應的 bean,即自定義的 View 的對象
- SpringMVC 調用自定義視圖的 renderMergedOutputModel 方法,渲染視圖
- 說明:如果 SpringMVC 調用 Handler 的目標方法時,返回的自定義 View ,在 IOC 容器中的 id 不存在,則仍然按照預設的視圖解析器機制處理。
Debug-01
(1)在GoodsHandler的目標方法中打上斷點:
(2)點擊debug,訪問view.jsp,點擊超鏈接,可以看到後臺游標跳轉到斷點處:
(3)在源碼 BeanNameViewResolver 的 resolveViewName 方法處打上斷點:
(4)點擊Resume,游標跳轉到了這個斷點處,viewName 的值就是自定義視圖對象的 id:這裡完成視圖解析
resolveViewName 方法如下:
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws BeansException {
//獲取ioc容器對象
ApplicationContext context = obtainApplicationContext();
//如果容器對象中不存在 目標方法返回的自定義視圖對象id
if (!context.containsBean(viewName)) {
// Allow for ViewResolver chaining...
//就返回null,讓預設的視圖解析器處理該視圖
return null;
}
//判斷自定義的視圖是不是 org.springframework.web.servlet.View 類型
if (!context.isTypeMatch(viewName, View.class)) {
//如果不是
if (logger.isDebugEnabled()) {
logger.debug("Found bean named '" + viewName + "' but it does not implement View");
}
// Since we're looking into the general ApplicationContext here,
// let's accept this as a non-match and allow for chaining as well...
return null;
}
//如果是,就返回這個自定義視圖對象
return context.getBean(viewName, View.class);
}
(5)在自定義視圖對象里打上斷點:
(6)點擊 resume,游標跳轉到該斷點:在這裡完成視圖渲染,並轉發到結果頁面
(7)最後由 tomcat 將數據返回給客戶端:
2.5Debug源碼-預設視圖解析器執行流程
將預設視圖解析器的優先順序調高:
debug-02
(1)仍然在GoodsHandler中添加斷點:
(2)瀏覽器訪問 view.jsp,可以看到後臺游標跳轉到了斷點處:
(3)分別在預設視圖解析器(InternalResourceViewResolver)和自定義視圖解析器(BeanNameViewResolver) 中的方法中打上斷點:
(4)點擊resume,可以看到游標先跳到了預設視圖解析器的 buildView 方法中:因為預設解析器的優先順序在之前設置為最高。
buildView 方法:
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
//根據目標方法返回的viewName創建一個View對象
InternalResourceView view = (InternalResourceView) super.buildView(viewName);
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
view.setPreventDispatchLoop(true);
return view;
}
這個 View 對象的 url 是按照你配置的首碼和尾碼,拼接完成的 url
(5)之後就會到該View對象進行視圖渲染,然後由Tomcat將數據返回給客戶端。
但是如果該url下沒有/WEB-INF/pages/liView.jsp文件,就會報錯:
2.6Debug源碼-自定義View不存在,會走預設視圖解析機制
視圖解析器可以配置多個,按照指定的順序來對視圖進行解析。如果上一個視圖解析器不匹配,下一個視圖解析器就會去解析視圖,以此類推:
-
在容器文件中,將預設的視圖解析器調用優先順序降低,提高自定義視圖解析器的調用優先順序。見2.2的容器文件配置
-
刪除2.2中的自定義視圖MyView.java。也就是說,自定義視圖解析器解析目標方法返回的視圖對象時,將會無法解析該視圖,因為它不存在。
-
這時就會去調用下一個優先順序的視圖解析器,即預設視圖解析器。
debug-03
(1)仍然在GoodsHandler中添加斷點:
(2)瀏覽器訪問 view.jsp,可以看到後臺游標跳轉到了斷點處:
(3)在自定義的視圖解析器 BeanNameViewResolver 中打上斷點:
(4)點擊resume,可以看到游標跳轉到該斷點處:
(5)因為在容器文件中找不到該視圖對象的id了,因此會進入第一個分支,方法直接返回 null
(6)點擊step over,游標跳轉到中央控制器的 resolveViewName 方法中:
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
//迴圈調用視圖解析器,直到某個視圖解析器返回的view不為null
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
因為自定義視圖解析器會返回 null,因此這裡進入第二次迴圈,由預設的視圖解析器去進行解析,然後返回對應的視圖:
(7)在該方法中打上斷點,點擊 resume,可以看到此時 view 是由預設的視圖解析器返回的視圖對象,走的是預設機制。
(8)下一個就按照預設機制拼接的 url 去訪問該頁面,併進行渲染。然後由Tomcat返回給客戶端。如果根據 url 找不到該頁面,就報404錯誤。
補充:如果預設視圖解析器優先順序高,自定義的視圖解析器優先順序低,但是預設視圖解析器返回的的View為null,這時候會繼續調用自定義的視圖解析器嗎?
答:事實上,預設視圖解析器返回的 View 不會為 null。
因為它是根據目標方法返回的字元串+你配置的前尾碼進行 url 的拼接。只要目標方法返回了一個字元串,預設視圖處理器就不會返回 null。
如果目標方法返回的是 null 呢?將會以目標方法的路徑名稱+配置的前尾碼作為尋找頁面的 url
因此在迴圈調用視圖處理器的時候,一旦迴圈到預設視圖處理器,就不會調用後面的自定義視圖解析器。
3.目標方法直接指定轉發或重定向
3.1使用實例
目標方法中指定轉發或者重定向:
- 預設返回的方式是請求轉發,然後用視圖處理器進行處理。
- 但是也可以在目標方法中直接指定重定向或者轉發的 url 的地址。
- 註意:如果指定重定向,則不能定向到 /WEB-INF 目錄中。因為該目錄為 Tomcat 的內部目錄。
例子
-
view.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>請求轉發或重定向</title> </head> <body> <h1>請求轉發或重定向</h1> <a href="goods/order">測試在目標方法找中指定請求轉發或重定向</a> </body> </html>
-
GoodsHandler.java
package com.li.web.viewresolver; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @author 李 * @version 1.0 */ @RequestMapping(value = "/goods") @Controller public class GoodsHandler { //演示直接指定請求轉發或者重定向 @RequestMapping(value = "/order") public String order() { System.out.println("=========order()========="); //請求轉發到 /WEB-INF/pages/my_view.jsp //下麵的路徑會被解析/web工程路徑/WEB-INF/pages/my_view.jsp return "forward:/WEB-INF/pages/my_view.jsp"; } }
-
訪問 view.jsp,點擊超鏈接:
-
成功進入到請求轉發的頁面:
請求轉發也可以轉發到WEB-INF目錄之外的頁面。
-
修改 GoodsHandler.java 的 order 方法:
//演示直接指定請求轉發或者重定向 @RequestMapping(value = "/order") public String order() { System.out.println("=========order()========="); //重定向 //1.對於重定向來說,不能重定向到/WEB-INF/目錄下 //2.redirect 為重定向的關鍵字 //3./login.jsp 是在伺服器解析的,解析為 /web工程路徑/login.jsp return "redirect:/login.jsp"; }
-
redeployTomcat,訪問 view.jsp,點擊超鏈接,可以看到成功重定向到 login.jsp頁面