Spring 視圖和視圖解析器簡介
什麼是 Spring 視圖和視圖解析器
Spring MVC(Model View Controller)是 Spring 中一個重要的組成部分,而 Spring 視圖和視圖解析器則是 Spring MVC 中的組成部分。在介紹 Spring 視圖和視圖解析器前,我們先了解下在 Spring MVC 框架中,一個 Web 請求所需經歷 的六個階段:
請求會首先被 Spring MVC 的前端請求分發器(Dispatcher)攔截。該攔截器是一個 Servlet, 需要在 web.xml 中配 置,所有符合所配置的 URL 樣式的訪問請求,將都會被該攔截器攔截。Spring 提供了默認的分發器 org.springframework.web.servlet.DispatcherServlet,您可以根據需要,決定是否需要定制自己的分發器。
在接收到訪問請求後,分發器會根據開發人員在 Spring 配置文件或代碼中的注解(Annotation),來查找合適的控制 器。
分發器在查找到合適的控制器後,將請求轉交給該控制器處理。
通常,控制器會調用相應服務類來處理業務邏輯,在將請求處理後,控制器需返回處理後的結果數據模型(Model)以及 下一個需要顯示的視圖名。
在控制器處理結束並返回模型和視圖名之後,Spring 會依次調用 Spring 容器中所注冊的視圖解析器,來查找符合條件 的視圖。
在獲得 Spring 視圖後,Spring 會根據該視圖的配置信息,顯示該視圖。
圖 1.Spring MVC 處理流程
通過以上 Spring MVC 的介紹,我們可以發 現,視圖和視圖解析器將出現在整個請求處理流程中的最後部分。那麼到底什麼是視圖和視圖解析器?簡而言之,視圖是指 Spring MVC 中的 V(View),而視圖解析器的功能則是依據指定的規則來查找相應的視圖。
常用視圖和視圖解析器 簡介
在開發中,視圖通常就是 JSP、Velocity 等。Spring 默認提供了多種視圖解析器,比如,我們可以使用最常用解析器 InternalResourceViewResolver 來查找 JSP 視圖(與之相對應的視圖類為 InternalResourceView)。通常,一個視圖解 析器只能查找一個或多個特定類型的視圖,在遇到 Spring 不支持的視圖或者我們要自定義視圖查找規則的情況下,我們就 可以通過擴展 Spring 來自定義自己所需的視圖解析器。目前,視圖解析器都需要實現接口 org.springframework.web.servlet.ViewResolver, 它包含方法 resolveViewName,該方法會通過視圖名查找並返回 Spring 視圖對象。表 1 列出了常用的 Spring 視圖解析器。
表 1.Spring 常用視圖解析器列表
在多數項目中,InternalResourceViewResolver 是最常用的,該解析器可以返回指定目錄下指定後綴的文件,它支持 JSP 及 JSTL 等視圖技術,但是用該視圖解析器時, 需要注意設置好正確的優先級,因為該視圖解析器即使沒有找到正確的文件,也會返回一個視圖,而不是返回 null,這樣 優先級比該視圖解析器低的解析器,將不會被執行。
在 Web 開發中,我們的前端顯示可以是 JSP、Excel、 Velocity 等,在 Spring 中,不同的前端顯示技術都有其對應的 Java 視圖類,正如表 1 所提到的, InternalResourceView 可以代表 JSP 視圖,FreeMarkerView 代表 FreeMarker 視圖。目前,Spring 支持多種技術開發的 視圖,包括 JSP、JSTL、Excel,Velocity 等,在多數項目中,用戶並不需要自定義自己的視圖,在接下來的章節,我們將 介紹如何定義開發視圖和視圖解析器。
示例場景介紹
在多數項目中,我們並不需要開發定制視圖解析器和視 圖,但在某些情況下,比如我們項目中的視圖格式並不是 Spring 所支持的,或者我們想使用自己的視圖和視圖解析器來更 靈活的處理視圖,此時,我們就可以開發自己的視圖解析器和視圖。
在本例中,我們提供了三種視圖:JSP 文件、 SWF 文件(flash 文件)以及一個自定義後綴名(.config)的文件,為了更方便的支持 SWF 視圖和自定義文件後綴名的視 圖,我們開發了自定義的視圖對象,希望能夠使用該視圖對象來支持 SWF 文件和 .config 文件,另外還開發了兩個視圖解 析器來實現本例。
開發 Spring 視圖
在 Spring 中,所有的視圖類都需要實現接口 org.springframework.web.servlet.view.View,如圖 2 所示,Spring 還提供了多個實現了 View 接口的抽象類,所以我 們並不需要直接實現接口 View, 而是可以實現 Spring 所提供的抽象類。本例中的自定義視圖類便是繼承了抽象類 org.springframework.web.servlet.view.AbstractUrlBasedView,通過繼承抽象類可以減輕定制開發的復雜度。
圖 2.Spring 視圖接口繼承體系圖
為了簡化程序開發和兼容更多的視圖,本例 開發的自定義視圖類為一個通用視圖類,它可以將請求文件的內容直接寫到請求響應(HTTP Response)中,並設置為該視 圖類所配置的內容類型(HTTP content-type),但該視圖類只能用於處理浏覽器能直接顯示的請求文件資源,如文本文件 、SWF 文件等,但它並不能支持 JSP 等需要編譯處理的文件。本例中,我們便是使用自定義視圖類 GenericFileView 來顯 示 SWF 和 .config 文本文件。清單 1 給出了通用視圖類代碼。
清單 1. 通用視圖類代碼
public class GenericFileView extends AbstractUrlBasedView { // 默認內容類型 private final static String CONTENT_TYPE = "text/plain"; //http response conent private String responseContent; public GenericFileView() { super(); setContentType(CONTENT_TYPE); } @Override public void setContentType(String contentType) { super.setContentType(contentType); } @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // 設置 content type response.setContentType(getContentType()); // 寫入 response 內容 response.getWriter().write(this.responseContent); response.getWriter().close(); } /** * 設置 http response content * @param responseContent */ public void setResponseContent(String responseContent) { this.responseContent = responseContent; } }
正如前文所述,本例中的視圖類繼承了抽象類 AbstractUrlBasedView,因此,我們的自定義通用視圖類並不需 太多代碼,其代碼主要就是設置文件內容類型和將響應的內容設置到請求響應對象中。
開發 Spring 視圖解析器
有了視圖類,我們還需要查找該視圖類的視圖解析器。所有的視圖解析器都需要實現接口 org.springframework.web.servlet.ViewResolver,但同視圖的實現一樣,Spring 還提供了一個抽象類,我們同樣可以通 過實現抽象類來節省開發工作。
在本例中,我們開發了自定義視圖解析器 GenericFileViewResolver,該類實現了 抽象類 org.springframework.web.servlet.view.AbstractCachingViewResolver,從圖 3 可以發現,常用的 Spring 中的 視圖解析器都繼承了該抽象類。
圖 3. 抽象類 AbstractCachingViewResolver 繼承體系圖
除了繼承抽象類 AbstractCachingViewResolver 外,本例中的視圖解析器還是實現了接口 org.springframework.core.Ordered,該接口用 於設置視圖解析器的調用優先級。這是因為,通常一個項目中會有多個視圖解析器,那麼就需要我們設定各個解析器被調用 的優先級,尤其是和 InternalResourceViewResolver 混用的情況下,必須要定義正確的調用優先級,正如我們在前面提到 的 InternalResourceViewResolver 會永遠返回一個視圖,即使在查找不到合適的視圖的情況下也不會返回 null,導致後 續的視圖解析器不會被調用。
視圖解析器的核心方法是 loadView,如清單 2 所示,在該方法中,需要根據請求的 文件路徑,找到該請求的文件,然後再生成一個新的視圖,並將文件流寫到視圖對象中。清單 2 為自定義的視圖解析器代 碼片段
清單 2. 自定義視圖解析器代碼片段
@Override protected View loadView(String viewName, Locale locale) throws Exception { if (location == null) { throw new Exception( "No location specified for GenericFileViewResolver."); } String requestedFilePath = location + viewName; Resource resource = null; try { logger.finest(requestedFilePath); resource = getApplicationContext().getResource(requestedFilePath); } catch (Exception e) { // 返回 null, 以便被下一個 resolver 處理 logger.finest("No file found for file: " + requestedFilePath); return null; } logger.fine("Requested file found: " + requestedFilePath + ", viewName:" + viewName); // 根據視圖名,獲取相應的 view 對象 GenericFileView view = this.getApplicationContext().getBean(this.viewName, GenericFileView.class); view.setUrl(requestedFilePath); // 寫入 view 內容 view.setResponseContent(inputStreamTOString(resource.getInputStream(), "ISO-8859-1")); return view; }
需要注意的是,在找不到請求文件的時候,需要返回 null,這樣 Spring 容器中所注冊的其他低優先級的視圖 解析器才能被調用。
開發復合視圖解析器
由於本例需要支持 SWF 及自定義後綴名的文件,所以我們期望能 夠根據不同請求的後綴名來調用不同的視圖解析器。實際上,Spring 已經提供了類似的視圖解析器- ContentNegotiatingViewResolver,它可以根據請求的文件後綴名或請求的 Accept 頭來查找視圖。 ContentNegotiatingViewResolver 本身並不負責查找視圖,它只是將視圖查找工作代理給所注冊的視圖解析器,清單 3 給 出了 ContentNegotiatingViewResolver 的配置文件片段。
清單 3.ContentNegotiatingViewResolver
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="mediaTypes"> <map> <entry key="atom" value="application/atom+xml"/> <entry key="html" value="text/html"/> <entry key="json" value="application/json"/> </map> </property> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </list> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" /> </list> </property> </bean>
從清單 3 可以發現,在使用 ContentNegotiatingViewResolver 時,一般需要配置三個部分:
注冊所支持的媒體類型,也就是文件後綴名及其對應的文件內容類型。
視圖解析器,也就是 ContentNegotiatingViewResolver 在查找視圖時,實際所代理使用的視圖解析器。
默認視圖,當視圖解析器沒有查 找到合適的視圖時,將使用該默認視圖。
關於 ContentNegotiatingViewResolver 的具體使用,讀者可以參見 Spring 官方文檔。
本例開發的復合視圖解析器和 ContentNegotiatingViewResolver 類似,雖然本例也可以使用 ContentNegotiatingViewResolver 來實現相同的功能,但 ContentNegotiatingViewResolver 較為復雜,它在查找視圖時 ,會將所有注冊到 ContentNegotiatingViewResolver 下的視圖解析器全部調用一遍,然後將所有查找到的視圖保存為候選 視圖,最後再根據篩選條件,篩選出一個最為合適的視圖。而本例中的復合視圖解析器則簡單實用的多,只需要注冊文件後 綴名和對應的視圖解析器即可,當為請求找到相應的視圖解析器時,便直接調用該視圖解析器,而不需調用其所注冊的所有 視圖解析器,清單 4 給出了在 Spring 配置文件中對該復合視圖解析的配置文件片段。
清單 4. 復合視圖解析器配 置片段
<bean id="viewResolver" class="com.sample.web.viewresolver.MultipleViewResolver" p:order="0"> <property name="resolvers"> <map> <entry key="config"> <bean class="com.sample.web.viewresolver.GenericFileViewResolver" p:location="/WEB-INF/config/" p:cache="false"> <property name="viewName" value="configFileView"/> </bean> </entry> <entry key="swf"> <bean class="com.sample.web.viewresolver.GenericFileViewResolver" p:location="/WEB-INF/swf/" p:cache="false"> <property name="viewName" value="swfFileView"/> </bean> </entry> </map> </property> </bean>
在配置文件中,我們為不同的文件後綴名注冊了相應的視圖解析器,並為該視圖解析器配置了 所對應查找的視圖類。同 ContentNegotiatingViewResolver 類似,本例中的復合視圖解析器 MultipleViewResolver 也是 將具體的視圖查找工作代理給所注冊的視圖解析器,實際上,MultipleViewResolver 也是一個普通的視圖解析器,不過在 核心方法 loadView(如清單 5)中,首先獲得請求視圖的後綴名,然後根據後綴名獲得所注冊的視圖解析器,最後,再使 用獲得的視圖解析器查找視圖。
清單 5 .MultipleViewResolver 代碼片段
@Override protected View loadView(String viewName, Locale locale) throws Exception { String fileExtension = StringUtils.getFilenameExtension(viewName); // 返回 null 如果沒有後綴名,以便下一個 resolver 處理 if (fileExtension == null) { return null; } // 通過後綴名獲取 resolver ViewResolver resolver = resolvers.get(fileExtension); //return null to invoke next resolver if no resolver found return resolver == null ? null : resolver.resolveViewName(viewName, locale); }
在准備好了自定義視圖和視圖解析後,我們就可以開發 Spring MVC 中的控制器來處理 Web 請求。
開發 控制器
在本例中,我們將要演示使用自定義視圖解析器處理 SWF 和 .config 文件訪問請求,另外還提供了處理 JSP 文件訪問請求的能力,所以,本例提供了一個簡單的控制器 SampleController,該控制器能夠處理三個請求路徑,並 返回相應的視圖,如清單 6 所示。
清單 6 .SampleController 代碼片段
@Controller public class SampleController { @RequestMapping("/jsp/sample") public String getSampleJsp() { return "SampleJsp"; } @RequestMapping("/config/sample.config") public String getSampleConfig() { return "SampleConfig.config"; } @RequestMapping("/swf/sample.swf") public String getSampleSwf() { return "SampleSwf.swf"; } }
根據以上代碼,控制器將為請求 /jsp/sample 返回視圖 SampleJsp,為請求 /config/sample.config 返回視圖 SampleConfig.config,並能夠為請求 /swf/sample.swf 返回視圖 SampleSwf.swf。
配置視圖解析器
在完成 控制器的開發後,我們就可以配置視圖及相應的解析器。在開發中,我們通常會將 Spring 的配置文件至少分為兩個部分, 在本例中,我們創建了兩個 Spring 配置文件 :spring-root.xml 和 spring-web.xml,如圖 4 所示。spring-root.xml 用 於存放非 web 管理相關的配置,而 spring-web.xml 則存放了所有與 web 相關的配置。由於本例比較簡單,spring- root.xml 文件中並沒有具體的配置內容。
圖 4. 示例項目文件結構圖
通過清單 6 可以發現,我們使用了注解 (Annotation)來定義控制器,所以,我們需要在 spring-web.xml 中配置 Spring 掃描的根目錄,如清單 7 所示。
清單 7 設置 Spring 注解掃描路徑
<context:component-scan base- package="com.sample.web.controller"/>
清單 4 中配置了復合視圖解析器,除此之外,我們還需 要定義解析器 jspViewResolver 來查找 JSP 文件,注意 order 屬性值為 1,大於復合視圖解析器的屬性值,這樣,我們 就可以首先使用自定義的復合視圖解析器查找視圖,當沒有找到合適的代理視圖解析器或視圖時,才會調用 jspViewResolver 來進行視圖查找。
清單 8. 配置 InternalResourceViewResolver
<!-- View Resolver for JSP files --> <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="1"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
在清單 4 中,我們還定義了所注冊的視圖解析器所對應的視圖,所以,在配置文件中,我們還需 要聲明相關的視圖,如清單 9 所示。
清單 9. 配置自定義視圖
<bean id="configFileView" class="com.sample.web.view.GenericFileView" p:contentType="text/plain" p:url="" scope="prototype"/> <bean id="swfFileView" class="com.sample.web.view.GenericFileView" p:contentType="application/x-shockwave-flash" p:url="" scope="prototype"/>
configFileView 用於表示一個文本文件視圖,本例對應一個後綴名為 .config 的文本 文件,swfFileView 用於表示一個 flash 文件視圖,本例為一個後綴名為 .SWF 的 flash 文件。
示例運行
至此,本例的開發工作就都已經完成了,示例控制器只支持三個請求路徑,但實際上,我們可以通過使用 @RequestMapping(value = "/path/{fileName}") 能夠讓控制器處理更為通用的請求。
在運行示例前, 需要准備好 Web 服務器和 Spring 運行環境,本例已經在 Tomcat 7.0 和 Spring 3.0.5 環境上測試通過,讀者可以下載 源代碼部署在自己的環境上。
當我們輸入請求地址 http://localhost:8080/SpringSample/swf/sample.swf, 控制 器將通過使用我們自定義的視圖解析器,查找到視圖文件 SampleSwf.swf,並將文件內容作為 application/x-shockwave- flash 顯示到浏覽器中,如圖 5 所示。
圖 5. 請求訪問 SWF 文件
處理自定義文件後綴名 .config 的處理流程 同處理 SWF 文件類似,差別之處在於,視圖的文件媒體類型為 text/plain,所以在浏覽器中,我們將會看到一個文本顯示 , 如圖 6 所示 .
圖 6. 請求訪問 .config 文件
另外,本例還支持 JSP 文件的訪問,在本例 中,當有 JSP 訪問請求時,自定義的視圖解析器將會被調用,但由於我們並沒有注冊查找空後綴名或 .jsp 請問的訪問, 所以自定義的視圖解析器將返回一個 null,接下來,Spring 會接著調用我們已注冊的 jspViewResolver(也就是 InternalResourceViewResolver)來繼續視圖查找,並最終返回所請求的 JSP 文件,如圖 7 所示。
圖 7. 請求訪 問 JSP 文件
總結
本文介紹了 Spring 視圖和視圖解析器的相關概念,並簡單介紹了 Spring 所提供的視圖和視圖解析器,並結 合示例介紹了如何開發自定義的視圖和視圖解析器。
下載