http://www.ibm.com/developerworks/cn/java/j-lo-sPRingview/
Spring 3.0 默認包含了多種視圖和視圖解析器,比如 jsp、Velocity 視圖等,但在某些情況下,我們需要開發自定義的視圖及其解析器,以便顯示特殊文件格式的視圖,我們也可以使用自定義視圖及解析器,針對特定的視圖做相應的處理。本文將通過一個示例來介紹如何開發 Spring 自定義視圖和視圖解析器,來顯示后綴名為 SWF 的視圖,并提供一個簡單的注冊機制,為特定后綴名的視圖注冊相應的視圖解析器。
Spring MVC(Model View Controller)是 Spring 中一個重要的組成部分,而 Spring 視圖和視圖解析器則是 Spring MVC 中的組成部分。在介紹 Spring 視圖和視圖解析器前,我們先了解下在 Spring MVC 框架中,一個 Web 請求所需經歷的六個階段:
通過以上 Spring MVC 的介紹,我們可以發現,視圖和視圖解析器將出現在整個請求處理流程中的最后部分。那么到底什么是視圖和視圖解析器?簡而言之,視圖是指 Spring MVC 中的 V(View),而視圖解析器的功能則是依據指定的規則來查找相應的視圖。
在開發中,視圖通常就是 JSP、Velocity 等。Spring 默認提供了多種視圖解析器,比如,我們可以使用最常用解析器 InternalResourceViewResolver 來查找 JSP 視圖(與之相對應的視圖類為 InternalResourceView)。通常,一個視圖解析器只能查找一個或多個特定類型的視圖,在遇到 Spring 不支持的視圖或者我們要自定義視圖查找規則的情況下,我們就可以通過擴展 Spring 來自定義自己所需的視圖解析器。目前,視圖解析器都需要實現接口 org.springframework.web.servlet.ViewResolver, 它包含方法 resolveViewName,該方法會通過視圖名查找并返回 Spring 視圖對象。表 1 列出了常用的 Spring 視圖解析器。
XmlViewResolver
接口 ViewResolver 的實現,從 XML 配置文件中查找視圖實現(默認 XML 配置文件為 /WEB-INF/views.xml).ResourceBundleViewResolver
接口 ViewResolver 的實現,用于從 properties 文件中查找視圖。.UrlBasedViewResolver
接口 ViewResolver 的實現,用于根據請求的 URL 路徑返回相應的視圖,該視圖需為抽象類 AbstractUrlBasedView 的實現,它還有些子類,如 InternalResourceView 和 JstlView 等 .InternalResourceViewResolver
UrlBasedViewResolver 的子類,通常用于查找 JSP(類 InternalResourceView)和 JSTL(類 JstlView,InternalResourceView 的子類)等視圖。VelocityViewResolver /FreeMarkerViewResolver
UrlBasedViewResolver 的子類分別用于支持 Velocity(類 VelocityView)和 FreeMark 視圖(類 FreeMarkerView)。ContentNegotiatingViewResolver
接口 ViewResolver 的實現,用于根據請求文件的后綴名或請求的 header 中的 accept 字段查找視圖。
在多數項目中,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 中,所有的視圖類都需要實現接口 org.springframework.web.servlet.view.View,如圖 2 所示,Spring 還提供了多個實現了 View 接口的抽象類,所以我們并不需要直接實現接口 View, 而是可以實現 Spring 所提供的抽象類。本例中的自定義視圖類便是繼承了抽象類 org.springframework.web.servlet.view.AbstractUrlBasedView,通過繼承抽象類可以減輕定制開發的復雜度。
為了簡化程序開發和兼容更多的視圖,本例開發的自定義視圖類為一個通用視圖類,它可以將請求文件的內容直接寫到請求響應(HTTP Response)中,并設置為該視圖類所配置的內容類型(HTTP content-type),但該視圖類只能用于處理瀏覽器能直接顯示的請求文件資源,如文本文件、SWF 文件等,但它并不能支持 JSP 等需要編譯處理的文件。本例中,我們便是使用自定義視圖類 GenericFileView 來顯示 SWF 和 .config 文本文件。清單 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,因此,我們的自定義通用視圖類并不需太多代碼,其代碼主要就是設置文件內容類型和將響應的內容設置到請求響應對象中。
有了視圖類,我們還需要查找該視圖類的視圖解析器。所有的視圖解析器都需要實現接口 org.springframework.web.servlet.ViewResolver,但同視圖的實現一樣,Spring 還提供了一個抽象類,我們同樣可以通過實現抽象類來節省開發工作。
在本例中,我們開發了自定義視圖解析器 GenericFileViewResolver,該類實現了抽象類 org.springframework.web.servlet.view.AbstractCachingViewResolver,從圖 3 可以發現,常用的 Spring 中的視圖解析器都繼承了該抽象類。
除了繼承抽象類 AbstractCachingViewResolver 外,本例中的視圖解析器還是實現了接口 org.springframework.core.Ordered,該接口用于設置視圖解析器的調用優先級。這是因為,通常一個項目中會有多個視圖解析器,那么就需要我們設定各個解析器被調用的優先級,尤其是和 InternalResourceViewResolver 混用的情況下,必須要定義正確的調用優先級,正如我們在前面提到的 InternalResourceViewResolver 會永遠返回一個視圖,即使在查找不到合適的視圖的情況下也不會返回 null,導致后續的視圖解析器不會被調用。
視圖解析器的核心方法是 loadView,如清單 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 的配置文件片段。
<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 的具體使用,讀者可以參見 Spring 官方文檔。
本例開發的復合視圖解析器和 ContentNegotiatingViewResolver 類似,雖然本例也可以使用 ContentNegotiatingViewResolver 來實現相同的功能,但 ContentNegotiatingViewResolver 較為復雜,它在查找視圖時,會將所有注冊到 ContentNegotiatingViewResolver 下的視圖解析器全部調用一遍,然后將所有查找到的視圖保存為候選視圖,最后再根據篩選條件,篩選出一個最為合適的視圖。而本例中的復合視圖解析器則簡單實用的多,只需要注冊文件后綴名和對應的視圖解析器即可,當為請求找到相應的視圖解析器時,便直接調用該視圖解析器,而不需調用其所注冊的所有視圖解析器,清單 4 給出了在 Spring 配置文件中對該復合視圖解析的配置文件片段。
<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)中,首先獲得請求視圖的后綴名,然后根據后綴名獲得所注冊的視圖解析器,最后,再使用獲得的視圖解析器查找視圖。
@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 所示。
@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 文件中并沒有具體的配置內容。
通過清單 6 可以發現,我們使用了注解(Annotation)來定義控制器,所以,我們需要在 spring-web.xml 中配置 Spring 掃描的根目錄,如清單 7 所示。
<context:component-scan base-package="com.sample.web.controller"/>
清單 4 中配置了復合視圖解析器,除此之外,我們還需要定義解析器 jspViewResolver 來查找 JSP 文件,注意 order 屬性值為 1,大于復合視圖解析器的屬性值,這樣,我們就可以首先使用自定義的復合視圖解析器查找視圖,當沒有找到合適的代理視圖解析器或視圖時,才會調用 jspViewResolver 來進行視圖查找。
<!-- 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 所示。
<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 所示。
處理自定義文件后綴名 .config 的處理流程同處理 SWF 文件類似,差別之處在于,視圖的文件媒體類型為 text/plain,所以在瀏覽器中,我們將會看到一個文本顯示, 如圖 6 所示 .
另外,本例還支持 JSP 文件的訪問,在本例中,當有 JSP 訪問請求時,自定義的視圖解析器將會被調用,但由于我們并沒有注冊查找空后綴名或 .jsp 請問的訪問,所以自定義的視圖解析器將返回一個 null,接下來,Spring 會接著調用我們已注冊的 jspViewResolver(也就是 InternalResourceViewResolver)來繼續視圖查找,并最終返回所請求的 JSP 文件,如圖 7 所示。
本文介紹了 Spring 視圖和視圖解析器的相關概念,并簡單介紹了 Spring 所提供的視圖和視圖解析器,并結合示例介紹了如何開發自定義的視圖和視圖解析器。
SpringSample.zip
新聞熱點
疑難解答