Portlet MVC框架


Portlet MVC框架


16.1. 介紹

 

Spring不僅支持傳統(基於Servlet)的Web開發,也支持JSR-168 Portlet開發。 Portlet MVC框架盡可能多地采用Web MVC框架,使用相同的底層表現層抽象和整合技術。所以, 在繼續閱讀本章前,務必溫習Chapter 13, Web框架Chapter 14, 集成視圖技術兩章。

[Note] Note

請牢記,在Spring MVC中的概念和Spring Porlet MVC中的相同的同時,JSR-168 Portlet 獨特的工作流程造成了一些顯著的差異。

Porlet工作流程和Servlet的主要差異在於,Portlet的請求處理有兩個獨特 的階段:動作階段和顯示階段。動作階段會有“后台”數據改變或動作的代碼,這些代碼 只會執行一次。顯示階段會產生用戶每次刷新時的看到的顯示內容。重要的是, 在單個請求的整個處理過程中,動作階段只會被執行一次,而顯示階段可能會被執行多次。 這就提供了(並且要求)在改變系統持久狀態的活動和產生顯示內容的活動之間 有一個清晰的分層。

這種兩階段的請求處理是JSR-168規范的一個優點,比如,可以自動地更新動態 的搜索結果,不需要用戶特意去再次執行搜索。許多其它的Portlet MVC框架試圖向開 發人員徹底隱藏這種兩階段處理,讓框架看上去盡可能和傳統的Servlet開發相同 - 在我們 看來,這種方式去掉了使用Portlet的一個主要好處,所以在Spring Portlet MVC 框架里分離的兩階段處理被保留了下來,這主要表現在,Servlet版本的MVC類將只 有一個方法來處理請求,而Portlet版本的MVC類里將會有兩個方法:一個用在動作 階段,另一個用在顯示階段。比如,在Servlet版本的 AbstractController有 handleRequestInternal(..)方法,Portlet版本的 AbstractController有 handleActionRequestInternal(..)和 handleRenderRequestInternal(..)方法。

這個框架是圍繞着分發器 DispatcherPortlet設計的,分發器把請求轉發給處理 器。和Web框架的DispatcherServlet一樣, 這個框架還有可配置的處理器映射和視圖解析,同時也支持文件上傳。

Portlet MVC不支持本地化解析和主題解析 - 它們是portal/portlet容器 的范疇,並不適合放在Spring框架里。但是,Spring里所有依賴本地化(比如消息的 國際化)仍舊可以工作,因為DispatcherPortlet在以 DispatcherServlet相同的方式暴露當前的本地化信息。

16.1.1. 控制器 - MVC中的C

 

缺省的處理器是一個非常簡單的 Controller接口,它提供了兩個方法:

  • void handleActionRequest(request,response)

  • ModelAndView handleRenderRequest(request,response)

這個框架包含了許多相同的控制器實現層次,比如, AbstractController, SimpleFormController等。它在數據綁定、命令對象使用、 模型處理和視圖解析等方面和Servlet框架相同。

16.1.2. 視圖 - MVC中的V

 

這個框架利用了一個特殊的橋Servlet ViewRendererServlet來使用Servlet框架里的視圖顯示 功能,這樣,Portlet請求就被轉化為Servlet請求,Portlet視圖能夠以通常的 Servlet底層代碼來顯示。這意味着,在Portlet里仍能使用當前所有的顯示方法, 如JSP、Velocity等。

16.1.3. Web作用范圍的Bean

 

Spring Portlet MVC支持Web Bean,這些Bean的生命周期在於當前的HTTP請求 或HTTP Session(一般的和全局的)里,這不是 框架自身的特性,而是由使用的容器的 WebApplicationContext提供的。 Section 3.4.3, “其他作用域”詳細地描述了這些Bean的作用范圍。

[Tip] Tip
???

Spring發布包帶有完整的Spring Portlet MVC示例, 這個應用演示了所有Spring Portlet MVC框架的功能和特色。

你可以在samples/petportal目錄下找到這個'petportal'應用。

16.2.  DispatcherPortlet

 

Portlet MVC是一個請求驅動的Web MVC框架,它圍繞着Portlet設計,把請求 轉發給控制器,提供了便利的Porltet應用開發功能。而且,Spring的 DispatcherPortlet功能遠遠不止這些,它和Spring ApplicationContext完全集成,使得開發人員 能夠使用Spring其它部分的每個功能。

DispatcherPortlet和一般的Portlet一樣, 在Web應用的portlet.xml中聲明:

<portlet>
	<portlet-name>sample</portlet-name>
	<portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
	<supports>
		<mime-type>text/html</mime-type>
		<portlet-mode>view</portlet-mode>
	</supports>
	<portlet-info>
		<title>Sample Portlet</title>
	</portlet-info>
</portlet>

現在需要配置DispatcherPortlet

在Portlet MVC框架里,每個 DispatcherPortlet都有自己的 WebApplicationContext,它接管了所有在根 WebApplicationContext定義的Bean。我們可以 在Portlet作用范圍內對這些Bean進行重載,重載后的Bean可以定義成對於特定 的Portlet實例可見。

在初始化 DispatcherPortlet時,框架會在Web應用的WEB-INF 目錄下尋找 [portlet-name]-portlet.xml,生成在其中定義的Bean(會覆蓋 在全局范圍里名字相同的Bean的定義)。

DispatcherPortlet用到的配置文件位置 可以通過Portlet初始化參數來修改(下面有詳細的描述)。

Spring的DispatcherPortlet會用一些特殊的Bean 來處理請求和顯示視圖。這些Spring包含的Bean和其它的Bean一樣,可以在 WebApplicationContext里進行配置。每 個Bean下面都會有詳細的描述。這里,只是讓你知道它們, 我們繼續討論DispatcherPortlet。大多數的Bean都有缺省 配置,所以你不需要擔心它們的配置。

Table 16.1.  WebApplicationContext 里的特殊的Bean

名詞 解釋
處理器映射 (Section 16.5, “處理器映射”) 一個前置和后置的處理器以及控制器的列表,這些控制器 通過匹配特定的條件(比如,由控制器指定的Portlet模式), 從而得到執行。
控制器 (Section 16.4, “控制器”)是MVC的一員, 是提供(或至少可以訪問)具體功能的Bean
視圖解析器 (Section 16.6, “視圖和它們的解析”) 能夠將 視圖名字對應到視圖定義。
分段(multipart)解析器 (Section 16.7, “Multipart文件上傳支持”) 能夠處理 從HTML表單上傳的文件
處理器異常解析器 (Section 16.8, “異常處理”) 能夠將異常對應到視圖,或實現某種復雜的異常處理代碼

DispatcherPortlet配置好后,請求進入到特定 DispatcherPortlet時,它開始處理。下面描述了 DispatcherPortlet處理請求的完整過程:

  1. PortletRequest.getLocale()返回 的Locale綁定在請求上,這使得在處理請求時(如顯示視圖、准備數據等), 代碼能夠使用Locale。

  2. 如果在ActionRequest里 指定了分段解析器,框架會在請求里尋找分段,如果找到了, 會把它們包裝在MultipartActionRequest 里,供在后續處理中使用。(關於分段處理的進一步信息見Section 16.7, “Multipart文件上傳支持” )。

  3. 尋找合適的處理器。如果找到了,這個處理器關聯的執行鏈 (前置處理器、后置處理器和控制器)會被按序執行來准備模型。

  4. 如果有模型返回,視圖通過視圖解析器進行顯示,視圖解析器是在 WebApplicationContext配置好的。如果沒有模型 返回(可能由於預處理器或后處理器攔截了請求,比如安全原因),就不會有視圖顯示 因為有可能請求已經被處理了。

WebApplicationContext里 定義的異常處理解析器能夠捕獲在處理請求時可能拋出的異常,借助這些解析器, 我們可以對在捕獲特定異常時的操作進行自定義。

通過在portlet.xml文件里增加Context參數或者Portlet 初始化參數,可以對Spring的DispatcherPortlet進行自定義。 下面列出了幾種可能。

Table 16.2.  DispatcherPortlet的初始化參數

參數 解釋
contextClass 實現WebApplicationContext 的類,在Portlet初始化時用它初始化context。如果沒有指定這個 參數,會使用XmlPortletApplicationContext
contextConfigLocation 傳給context實例(由contextClass指定) 的字符串,指明context的位置。它可以(以逗號)分隔為多個字符串來 支持多個context(在定義過兩次的bean有多個context位置時, 最后的位置起作用)。
namespace WebApplicationContext 的命名空間,缺省是[portlet-name]-portlet
viewRendererUrl ViewRendererServlet的URL, DispatcherPortlet可以訪問。 (見 Section 16.3, “ViewRendererServlet”)。

16.3. ViewRendererServlet

 

Portlet MVC中的顯示過程比Web MVC的復雜一點,為了復用所有Spring Web MVC里 的視圖技術,必須把 PortletRequest / PortletResponse 轉換到 HttpServletRequest / HttpServletResponse,然后調用 View的 render方法。為此,DispatcherPortlet 使用了一個特殊的servlet:ViewRendererServlet

為了DispatcherPortlet能夠顯示, 必須在web.xml文件里為你的web應用聲明一個 ViewRendererServlet的實例,如下:

<servlet>
    <servlet-name>ViewRendererServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>ViewRendererServlet</servlet-name>
    <url-pattern>/WEB-INF/servlet/view</url-pattern>
</servlet-mapping>

在實際執行顯示時,DispatcherPortlet這樣做:

  1. 把 WebApplicationContext作為屬性綁定在請求上, 使用和DispatcherServlet相同的 WEB_APPLICATION_CONTEXT_ATTRIBUTEkey。

  2. Model和 View對象綁定在請求上,使它們對 ViewRendererServlet可見。

  3. 構造 PortletRequestDispatcher對象,利用 映射到ViewRendererServlet/WEB- INF/servlet/viewURL來執行include操作。

然后,ViewRendererServlet能夠以合適的參數 調用Viewrender方法。

可以通過DispatcherPortletviewRendererUrl 配置參數來修改ViewRendererServlet的實際URL。

16.4. 控制器

 

Portlet MVC里的控制器和Web MVC的很想相似,在兩者之間移植代碼應該很簡單。

Portlet MVC控制器構架的基礎是 org.springframework.web.portlet.mvc.Controller 接口,如下所示。

public interface Controller {

    /**
     * Process the render request and return a ModelAndView object which the
     * DispatcherPortlet will render.
     */
    ModelAndView handleRenderRequest(RenderRequest request, RenderResponse response)
        throws Exception;

    /**
     * Process the action request. There is nothing to return.
     */
    void handleActionRequest(ActionRequest request, ActionResponse response)
        throws Exception;

}

如你所見,Portlet Controller接口需要兩個方法來處理Portlet 請求的兩個階段:動作請求和顯示請求。動作階段應該能夠處理動作請求,顯示階段應該 能夠處理顯示請求,並返回合適的模型和視圖。 盡管Controller接口是抽象的,但Spring Portlet MVC 提供了很多包含了各種各樣你需要的功能的控制器-它們中的大多數和Spring Web MVC里的控制器很類似。 Controller接口只定義每個控制器需要的通用的功能 - 處理動作請求,處理顯示請求,返回模型和視圖。

16.4.1.  AbstractControllerPortletContentGenerator

 

當然,僅一個Controller 是不夠的。為了提供基本的功能,所有的Spring Portlet Controller從 AbstractController繼承,后者可以訪問Spring 的ApplicationContext和控制緩存。

Table 16.3.  AbstractController提供的功能

參數 解釋
requireSession 表明當前的 Controller是否需要session。 所有的控制器都能使用這個功能。如果這樣的控制器收到請求時, session不存在,用戶會收到 SessionRequiredException
synchronizeSession 如果需要控制器在處理用戶session時保持同步,使用 這個參數。更具體來說,擴展的控制器會覆蓋handleRenderRequestInternal(..) 和handleActionRequestInternal(..)方法,如果指定了這個參數, 這兩個方法會在處理用戶session時保持同步。
renderWhenMinimized 如果需要在portlet最小化狀態時,控制器也顯示視圖, 把這個參數設為true。這個參數缺省是false,所以portlet在最小化狀態 時,不顯示內容。
cacheSeconds 在需要控制器覆蓋當前portlet定義的缺省緩存失效時間時, 設置一個正的整數。這個參數缺省是-1, 表示不改變缺省的緩存,把它設為0,就是 確保不緩存結果。

requireSession和 cacheSeconds屬性是在 AbstractController的父類 PortletContentGenerator里聲明的。為了完整性, 把它們列在這里。

在你自己的控制器里繼承AbstractController時 (不推薦這樣做,因為已經有許多現成的控制器,它們可能有你需要的功能),僅需要覆蓋 handleActionRequestInternal(ActionRequest, ActionResponse)方法或 handleRenderRequestInternal(RenderRequest, RenderResponse)方法(或兩者都覆蓋),實現邏輯, 並返回 ModelAndView 對象 (如果是 handleRenderRequestInternal方法)。

handleActionRequestInternal(..)和 handleRenderRequestInternal(..)方法的缺省實現都會 拋出 PortletException,這和JSR-168規范API里的 GenericPortlet的行為是一致的。所以只要覆蓋你的控制器 需要處理的方法。

下面簡短的例子包含了一個類和一個在web應用context里的聲明。

package samples;

import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.springframework.web.portlet.mvc.AbstractController;
import org.springframework.web.portlet.ModelAndView;

public class SampleController extends AbstractController {

    public ModelAndView handleRenderRequestInternal(
        RenderRequest request,
        RenderResponse response) throws Exception {

        ModelAndView mav = new ModelAndView("foo");
        mav.addObject("message", "Hello World!");
        return mav;
    }
}

<bean id="sampleController" class="samples.SampleController">
    <property name="cacheSeconds" value="120"/>
</bean>

為了使得一個簡單的控制器工作,你只需要類似上面的類和在web應用context里的聲明, 並且再設置一下處理器映射 (見 Section 16.5, “處理器映射”)。

16.4.2. 其它簡單的控制器

 

盡管你能夠繼承AbstractController, Spring Portlet MVC提供了不少具體的實現,它們提供了許多在簡單MVC應用里 常用的功能。

ParameterizableViewController基本上 和上面的例子類似,除了你能指定web應用context返回的視圖的名字。 (不需要寫死視圖名)。

PortletModeNameViewController把當前的 Portlet的狀態作為視圖名,如果Portlet在View模式 (比如:PortletMode.VIEW),那“View”就是視圖名。

16.4.3. Command控制器

 

Spring Portlet MVC提供了和Spring Web MVC完全一致的 command controllers層次結構,提供方法來與數據對象交互 並且動態地把參數從PortletRequest 綁定到數據對象上。數據對象不需要實現框架相關的接口,因而你可以 直接操作這些持久化對象。下面讓我們查看Command控制器提供的功能, 來了解它們的使用:

  • AbstractCommandController - Command控制器,可以用來創建自己的控制器,它能夠將請求里的參數 綁定到指定的數據對象。這個類不提供表單功能,但它提供驗證功能,並且 可以在控制器里指定如何處理帶有請求參數的Command對象.

  • AbstractFormController - 提供表單提交支持的抽象控制器。你能夠對表單進行建模,通過從控制器 里得到的Command對象來填充表單。在用戶提交表單后, AbstractFormController會綁定字段、進行驗證, 然后把對象返回給控制器來做下一步的動作。支持的功能有:無效表單提交(重新 提交)、驗正和通常的表單流程。你需要實現方法來決定表單的顯示和成功時使用的 視圖。如果你需要表單,但不想在應用context里指定用戶看到的視圖,使用這個 控制器。

  • SimpleFormController - 一個具體的AbstractFormController, 對使用對應的command對象生成表單提供了更多的支持。 SimpleFormController可以讓你在用戶成功地提交 表單或其它狀態時,指定command對象,表單的視圖名以及頁面對應的視圖名。

  • AbstractWizardFormController – 具體的AbstractFormController,它提交了向導式的接口 來編輯跨多個頁面的command對象。支持多種用戶動作:完成、取消或者頁面變化,所有這些 都可以簡便地在視圖的請求參數里指定。

這些command控制器是非常強大的,為了有效地使用,需要對它們的原理有 細致的理解。在你開始使用它們前,務必仔細閱讀它們層次結構的javadoc以及示例。

16.4.4.  PortletWrappingController

 

除了開發新的控制器,我們可以重用現有的portlet並且在 DispatcherPortlet 把請求映射指向它們。通過 PortletWrappingController,你能實例化一個 現有的Portlet來作 Controller,如下所示:

<bean id="wrappingController"
      class="org.springframework.web.portlet.mvc.PortletWrappingController">
    <property name="portletClass" value="sample.MyPortlet"/>
    <property name="portletName" value="my-portlet"/>
    <property name="initParameters">
        <value>
            config=/WEB-INF/my-portlet-config.xml
        </value>
    </property>
</bean>

這會很有價值,因為可以使用攔截器來對送向這些portlet的請求進行預處理和后處理。 而且也很方便,因為JSR-168沒有提供對過濾機制的支持。比如,可以在一個MyFaces JSR Portlet外面加上Hibernate的 OpenSessionInViewInterceptor

16.5. 處理器映射

 

通過處理器映射,可以把進來的portlet請求對應到合適的處理器上。已經有一些 現成的處理器映射可以使用,比如PortletModeHandlerMapping。 但還是讓我們先看一下HandlerMapping的一般概念。

注意,我們這里有意使用“處理器”來代替“控制器”。 DispatcherPortlet是設計用來和多種方式一起處理請求的, 而不僅僅是和Spring Portlet MVC自己的控制器。處理器是任意可以處理Portlet請求的對象。 控制器當然缺省是一種處理器。要將DispatcherPortlet和一些其他的框架一起使用,只需要實現相應的HandlerAdapter就可以了。

HandlerMapping提供的基本功能是提供一個 HandlerExecutionChain,后者必須包含匹配進來請求的 的處理器,也可能包含需要應用到請求的處理器攔截器的列表。當一個請求進來時, DispatcherPortlet會把它交給處理器射映,讓它來檢查 請求並得到合適的HandlerExecutionChain。然后 DispatcherPortlet會執行處理器以及chain里的攔截器。這些 概念和Spring Web MVC里的完全一致。

可配置的處理器映射非常強大,它可以包含攔截器(在實際的處理前、后進行預處理或后處理 或兩者都執行)。可以通過自定義一個HandlerMapping來加入許多功能。 想像一下,一個自定義的處理器映射,它不僅可以根據指定的portlet模式來選擇處理器, 也可以根據請求相聯系的session里的指定狀態來選擇。

在Spring Web MVC里,處理器映射通常是基於URL的。因為在Portlet里確實沒有URL, 必須使用其它的機制來控制映射。最常見的兩個是portlet模式和請求參數, 但在portlet請求里的任何對象都可以用在自定義的處理器映射中。

余下的章節會介紹在Spring Portlet MVC里最常見的三種處理器射映, 它們都繼承AbstractHandlerMapping並且共享以下的屬性:

  • interceptors: 需要使用的攔截器列表。 HandlerInterceptor在 Section 16.5.4, “增加 HandlerInterceptor”有討論。

  • defaultHandler: 在找不到匹配的處理器時, 缺省的處理器。

  • order: Spring會按照order屬性值 (見org.springframework.core.Ordered接口) 對context里的所有處理器映射進行排序,並且應用第一個匹配的處理器。

  • lazyInitHandlers: 用來Lazy初始化單例 處理器(prototype處理器是始終lazy初始化的)。缺省值是false。這個屬性是在這三個 具體處理器里直接實現。

16.5.1.  PortletModeHandlerMapping

 

這是一個簡單的處理器映射,它是基於當前的portlet模式(比如:'view', 'edit', 'help'). 如下:

<bean id="portletModeHandlerMapping"
      class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
    <property name="portletModeMap">
	    <map>
            <entry key="view" value-ref="viewHandler"/>
            <entry key="edit" value-ref="editHandler"/>
            <entry key="help" value-ref="helpHandler"/>
        </map>
    </property>
</bean>

16.5.2.  ParameterHandlerMapping

 

如果需要在不改變portlet模式的情況下而在多個控制器間切換, 最簡單的方法是把一個請求參數作為key來控制映射。

ParameterHandlerMapping使用一個特定的請求參數來控制映射。 這個參數的缺省名是'action',可以通過'parameterName'屬性來改變。

這個映射的bean設置會是這樣:

<bean id="parameterHandlerMapping"
class="org.springframework.web.portlet.handler.ParameterHandlerMapping"/>
    <property name="parameterMap">
        <map>
            <entry key="add" value-ref="addItemHandler"/>
            <entry key="edit" value-ref="editItemHandler"/>
            <entry key="delete" value-ref="deleteItemHandler"/>
        </map>
    </property>
</bean>

16.5.3.  PortletModeParameterHandlerMapping

 

最強大的內置處理映射 PortletModeParameterHandlerMapping結合了前兩者的功能, 能夠在每種portlet模式下進行不同的切換。

同樣,參數的缺省名是"action",但可以通過parameterName來修改。

缺省情況下,同樣的參數值不能在兩個不同的portlet模式下使用, 因為如果portlet自己改變了portlet模式,那么請求在映射中將不在有效。 把allowDupParameters屬性設為true可以改變這種行為,但這種做法是不推薦的。

這個映射的bean設置會是這樣:

<bean id="portletModeParameterHandlerMapping"
      class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping">
    <property name="portletModeParameterMap">
        <map>
            <entry key="view"> 
				<!-- 'view' portlet模式 -->
                <map>
                    <entry key="add" value-ref="addItemHandler"/>
                    <entry key="edit" value-ref="editItemHandler"/>
                    <entry key="delete" value-ref="deleteItemHandler"/>
                </map>
            </entry>
            <entry key="edit"> 
				<!-- 'edit' portlet模式 -->
                <map>
                    <entry key="prefs" value-ref="prefsHandler"/>
                    <entry key="resetPrefs" value-ref="resetPrefsHandler"/>
                </map>
            </entry>
        </map>
    </property>
</bean>

這個映射可以在處理鏈中放在 PortletModeHandlerMapping前面,它可以為每個模式以及全局提供 缺省的映射。

16.5.4. 增加 HandlerInterceptor

 

Spring的處理器映射機制里有處理器攔截器的概念,在希望對於特定的請求 應用不同的功能時,它是非常有用。比如,檢查用戶名(principal)。同樣,Spring Portlet MVC以Web MVC相同的方式實現了這些概念。

在處理器映射里的攔截器必須實現org.springframework.web.portlet 里的HandlerInterceptor接口。 和servlet的版本一樣,這個接口定義了三個方法:一個在實際的處理器執行前被調用 (preHandle),一個在執行后被調用(postHandle) 還有一個是在請求完全結束時被調用(afterCompletion)。 這三個方法應該可以為各種前置和后置處理提供足夠的靈活。

preHandle返回一個布爾值。可以使用這個方法來中斷或者繼續執行鏈的處理。 當返回true時,處理執行鏈會繼續,當返回false時, DispatcherPortlet 假設這個攔截器已經處理請求(比如,顯示了合適的視圖)並且不需要繼續執行其它的 攔截器和在執行鏈中實際的處理器。

postHandle只會在RenderRequest 中被調用。ActionRequestRenderRequest 都會調用preHandleafterCompletion方法。 如果希望只在其中的一種請求中執行你的代碼,務必在處理前檢查請求的類型。

16.5.5. HandlerInterceptorAdapter

 

和servlet包類似,portlet包里也有一個HandlerInterceptor的具體實現 - HandlerInterceptorAdapter。這個類所有方法都是空的, 所以可以繼承它,實現一個或兩個你所需要的方法。

16.5.6. ParameterMappingInterceptor

 

Portlet包也帶一個名為ParameterMappingInterceptor 的具體攔截器,它可以和ParameterHandlerMapping 以及PortletModeParameterHandlerMapping一起使用。 這個攔截器可以把用來控制映射的參數從ActionRequest 帶到隨后的RenderRequest,這能夠確保 RenderRequest映射到和ActionRequest相同的處理器。這些都是在 preHandle方法里完成的,所以在你的處理器里仍然可以改變決定 RenderRequest映射的參數值。

注意這個攔截器會調用ActionResponse 的setRenderParameter方法,這意味着在使用它的時候, 不能在處理器里調用sendRedirect。如果確實需要重定向, 可以手工地把映射參數向前傳,或者另寫一個攔截器來處理。

16.6. 視圖和它們的解析

 

如上面提到的那樣,Spring Portle MVC直接重用所有Sprint Web MVC里的視圖技術。 不僅包含了不同的View實現,也包含了視圖解析器的實現。 需要更多相關信息,請參考Chapter 14, 集成視圖技術Section 13.5, “視圖與視圖解析”

以下是一些在ViewViewResolver 中值得提及的:

  • 大多數的門戶希望portlet的顯示結果是HTML片斷,所以像 JSP/JSTL,Velocity,FreeMaker和XSLT是行得通的。但有時候視圖也可能在portlet 里返回其它類型的文檔。

  • 在portlet里不存在HTTP的重定向(ActionResponse 的sendRedirect(..)不能在portal中使用)。所以在Portlet MVC中 RedirectView'redirect:'前綴是 工作的。

  • 在Portlet MVC里可以使用'forward:'前綴。 但是,記住,在portlet里,當前URL是不確定的,這意味着不能使用相對URL來 訪問web應用的資源,必須使用絕對URL。

對於JSP開發,新的Spring Taglib和Spring表單taglib會以在Servlet視圖里相同的方式 在portlet視圖里工作。

16.7. Multipart文件上傳支持

 

Spring Portlet MVC和Web MVC一樣,也支持multipart來處理portlet中的文件上傳。 插件式的PortletMultipartResolver提供了對multipart的支持, 它在org.springframework.web.portlet.multipart包里。 Spring提供了PortletMultipartResolver來和 Commons FileUpload一起使用。余下的篇幅會介紹文件上傳的支持。

缺省情況下,Spring Portlet是不會處理multipart的,如果開發人員需要處理multipart, 就必須在web應用的context里添加一個multipart解析器,然后, DispatcherPortlet會在每個請求里檢查是否帶有multipart。 如果沒找到,請求會繼續,如果找到了multipart,在context中聲明的 PortletMultipartResolver會被調用。接着, 在請求里的multipart屬性會和其它的屬性一樣被處理。

16.7.1. 使用PortletMultipartResolver

 

下面的例子介紹了 CommonsPortletMultipartResolver的使用:

<bean id="portletMultipartResolver"
    class="org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver">

    <!-- 一個屬性;以byte為單位的最大文件長度 -->
    <property name="maxUploadSize" value="100000"/>
</bean>

當然為了使multipart解析器能夠工作,必須把合適的jar放到類路徑里。對於 CommonsMultipartResolver來說,需要 commons-fileupload.jar。注意,必須使用至少1.1 版本的Commons FileUpload,因為以前的版本不支持JSR-168應用。

現在你已經看到如何設置Portlet MVC來處理multipart請求,接下來我們 討論它的使用。當DispatcherPortlet檢測到 multipart時,它會激活在context里聲明的解析器,並把請求交給它。然后解析器 把當前的ActionRequest放到支持文件上傳的MultipartActionRequest中。通過 MultipartActionRequest,可以得到 請求包含的multipart信息,並且在控制器里訪問multipart文件。

注意,不能從RenderRequest接收到multipart 文件,而只能從ActionRequest里。

16.7.2. 處理表單里的文件上傳

 

在 PortletMultipartResolver處理完后, 請求會繼續被處理。你需要創建一個帶有上傳字段的表單來使用它(見下面),Spring會 把文件綁定在你的表單上(支持對象)。為了讓用戶上傳文件,必須創建一個 (JSP/HTML)的表單:

<h1>Please upload a file</h1>
<form method="post" action="<portlet:actionURL/>" enctype="multipart/form-data">
    <input type="file" name="file"/>
    <input type="submit"/>
</form>

如你所見,我們在bean的屬性后面創建名為“File”的字段 用來容納byte[]。加上了編碼屬性(enctype="multipart/form-data"), 讓瀏覽器知道怎樣來編碼multipart字段(不要忘記!)。

和其它那些不會自動轉化為字符串或原始類型的屬性一樣,為了把二進制數據放到對象 里,必須注冊一個使用PortletRequestDataBinder 的自定義的編輯器。現成有好幾個編輯器可以用來處理文件並把結果放到對象上。 StringMultipartFileEditor能夠把文件轉換成字符串 (使用用戶定義的字符集),ByteArrayMultipartFileEditor 能夠把文件轉換成字節數據。他們的功能和 CustomDateEditor一樣。

所以,為了能夠使用表單來上傳文件,需要聲明解析器,映射到處理這個bean的控制器的 映射以及控制器。

<bean id="portletMultipartResolver"
      class="org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver"/>

<bean id="portletModeHandlerMapping"
      class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
    <property name="portletModeMap">
        <map>
            <entry key="view" value-ref="fileUploadController"/>
        </map>
    </property>
</bean>

<bean id="fileUploadController" class="examples.FileUploadController">
    <property name="commandClass" value="examples.FileUploadBean"/>
    <property name="formView" value="fileuploadform"/>
    <property name="successView" value="confirmation"/>
</bean>

接着,創建控制器以及實際容納這個文件屬性的類。

public class FileUploadController extends SimpleFormController {

    public void onSubmitAction(
        ActionRequest request,
        ActionResponse response,
        Object command,
        BindException errors)
        throws Exception {

        // 類型轉換bean
        FileUploadBean bean = (FileUploadBean) command;

        // 是否有內容
        byte[] file = bean.getFile();
        if (file == null) {
            // 奇怪,用戶什么都沒有上傳
        }

        // do something with the file here
    }

    protected void initBinder(
            PortletRequest request, PortletRequestDataBinder binder)
        throws Exception {
        // to actually be able to convert Multipart instance to byte[]
        // we have to register a custom editor
        binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
        // 現在Spring知道如何來處理和轉換multipart對象
    }
}

public class FileUploadBean {

    private byte[] file;

    public void setFile(byte[] file) {
        this.file = file;
    }

    public byte[] getFile() {
        return file;
    }
}

如你所見,FileUploadBean有一個類型是 byte[]的屬性來容納文件。控制器注冊了一個自定義編輯器來 讓Spring知道如何把解析器發現的multipart轉換成指定的屬性。在這個例子里, 沒有對bean的byte[]屬性進行任何操作,但實際上,你可以做任 何操作(把它存到數據庫里,把它電郵出去,或其它)

下面是一個例子,文件直接綁定在的一個(表單支持)對象上的字符串類型屬性上面:

public class FileUploadController extends SimpleFormController {

    public void onSubmitAction(
        ActionRequest request,
        ActionResponse response,
        Object command,
        BindException errors) throws Exception {

        // cast the bean
        FileUploadBean bean = (FileUploadBean) command;

        // let's see if there's content there
        String file = bean.getFile();
        if (file == null) {
            // hmm, that's strange, the user did not upload anything
        }

        // do something with the file here
    }

    protected void initBinder(
        PortletRequest request, PortletRequestDataBinder binder) throws Exception {

        // to actually be able to convert Multipart instance to a String
        // we have to register a custom editor
        binder.registerCustomEditor(String.class,
            new StringMultipartFileEditor());
        // now Spring knows how to handle multipart objects and convert
    }
}

public class FileUploadBean {

    private String file;

    public void setFile(String file) {
        this.file = file;
    }

    public String getFile() {
        return file;
    }
}

當然,最后的例子在上傳文本文件時才有(邏輯上的)意義(在上傳圖像文件時, 它不會工作)。

第三個(也是最后一個)選項是,什么情況下需要直接綁定在(表單支持)對象的 MultipartFile屬性上。在以下的情況, 不需要注冊自定義的屬性編輯器,因為不需要類型轉換。

public class FileUploadController extends SimpleFormController {

    public void onSubmitAction(
        ActionRequest request,
        ActionResponse response,
        Object command,
        BindException errors) throws Exception {

        // cast the bean
        FileUploadBean bean = (FileUploadBean) command;

        // let's see if there's content there
        MultipartFile file = bean.getFile();
        if (file == null) {
            // hmm, that's strange, the user did not upload anything
        }

        // do something with the file here
    }
}

public class FileUploadBean {

    private MultipartFile file;

    public void setFile(MultipartFile file) {
        this.file = file;
    }

    public MultipartFile getFile() {
        return file;
    }
}

16.8. 異常處理

 

和Web MVC一樣,Portlet MVC提供了 HandlerExceptionResolver來減輕處理 請求處理產生的意外異常時的痛苦。Portlet MVC同樣也提供了具體的 SimpleMappingExceptionResolver,可以將可能拋出的 異常對應到一個視圖名。

16.9. Portlet應用的部署

 

Spring Portlet MVC應用的部署過程和JSR-168 Portlet應用的一樣。然而, 這部分內容常常使人感到困惑,所以值得在這里簡單地介紹一下。

通常情況下,portal/portlet容器在servlet容器的某個Web應用中運行, 你的Portlet運行在servlet容器的另一個Web應用里。為了使得Portlet容器能夠調用 Portlet應用,Portlet容器必須對一個顯式的Servlet進行跨Context的調用,那個Servlet 提供了對在portlet.xml定義的Portlet服務的訪問支持。

JSR-168規范對這方面沒有規定,所以每個Portlet容器都有自己的機制,通常 會引入一些“布署時的處理”來改變Portlet應用並且把Portlet注冊到Portlet容器里。

至少,在Portlet應用中web.xml文件需要通過修改來注入 Portlet容器會顯式調用的Servlet。有時候,單個Servlet實例對Web應用中的所有 Portlet提供支持,有時候,對於每個Portlet需要一個Servlet實例。

有些Portlet容器也會在Web應用中注入類庫或者配置文件。Portlet容器需要 實現Portlet JSP Tab庫以供使用。

最重要的是理解你選擇的portal對Portlet布署的要求,並且確保滿足它們 (通常是按照它提供的自動布署程序)。仔細閱讀portal這方面的文檔。

在你布署完Portlet后,檢查web.xml。有些老的portal 會破壞ViewRendererServlet的定義,破壞你的Portlet 顯示。


 


免責聲明!

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



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