-
請求生命中的第一天:請求從離開瀏覽器開始知道獲得一個響應,期間會有幾次停留,每一次都留下一些信息并得到更多的信息。(見PPT1)
-
配置DispatcherServlet:Spring MVC的核心是DispatcherServlet,這個servlet的功能是作為Spring MVC的前端控制器。和任何Servlet一樣,必須在Web應用系統的web.xml文件中配置。
<servlet>
<servlet-name>roadrantz</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
當DispatcherServlet載入后,它將從XML中載入Spring的應用上下文,這個XML的名字取決于Servlet的名字。在本例中,因為Servlet的名字叫做roadrantz,所以DispatcherServlet將試圖從一個叫做roadrantz-servlet.xml的文件中載入應用上下文。接下來指定哪些URL需要由DispatcherServlet來處理。
<servlet-mapping>
<servlet-name>roadrantz</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping> 我們可以為DispatcherServlet選擇任意URL樣式。我們選擇"*.htm"樣式的主要原因是因為這種樣式是大多數生成HTML內容的Spring MVC應用系統的慣例用法。另一個原因是生成的內容是HTML,因此URL應該反映這一點。
-
分解應用上下文:正如前面提到的,DispatcherServlet從以<servlet-name>命名的XML文件中載入應用上下文。但這不是說你不能將你的應用上下文切到多個XML文件中。事實上,我們建議你將應用上下文分散到應用系統的各個層中。(見PPT2)由于DispatcherServlet的配置文件是roadrantz-servlet.xml,所以在這個文件中應該包含用于控制器和其他Spring MVC組件的<bean>定義信息。對于業務層和數據層的Bean,我們傾向于將他們分別放到roadrantz-service.xml和roadrantz-data.xml中。
配置上下文載入器:為了保證所有的配置文件都被載入,需要在web.xml中配置一個上下文載入器。上下文載入器載入除DispatcherServlet載入的配置文件之外的其他上下文配置文件。最常用的上下文載入器是一個Servlet監聽器,其名稱為ContextLoaderListener,你需要在web.xml文件中像下面這樣配置它:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> 使用ContextLoaderListener配置時,你需要告訴它Spring配置文件的位置。如果沒有指定,上下文載入器會在/WEB-INF/applicationContext.xml中找Spring配置文件。但是這樣無法將應用上下文分散到應用系統的各個層中,所以你需要取代這種默認方式。 你可以通過Servlet上下文中設置contextConfigLocation參數來為上下文載入器指定一個或多個Spring配置文件:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/roadrantz-service.xml,
/WEB-INF/roadrantz-data.xml,
/WEB-INF/roadrantz-security.xml
</param-value>
</context-param>
Spring MVC概述:每一個Web應用程序都有一個主頁。這是應用程序的一個起始點。用戶可以從主頁中啟動應用程序,也可以在失去方向時回到主頁。否則,用戶會不停的點擊鏈接,感到很困擾,甚至可能離開,進入到其他網站中。第一步是建立一個處理主頁請求的控制器對象。因此讓我們來編寫第一個Spring MVC控制器。
-
創建控制器:在Spring MVC中,控制器是一個與應用程序功能的接口類。(見PPT3)控制器接收請求,將請求發送給服務類進行處理,最后又收集需要返回給用戶Web瀏覽器的結果。
public class HomePageController extends AbstractController {
protect ModelAndView handleRequestInternal(HttpServletRequest request,HttpServletResponse response)throws Exception {
List recentRants = rantService.getRecentRants( );
return new ModelAndView("home","rants",recentRants);
}
private RantService rantService;
public void setRantService(RantService rantService) {
this.rantService = rantService;
}
}
} Spring控制器和Servlet或Struts Action的不同之處在于它被配置成Spring應用上下文的一個普通JavaBean。這意味著,使用控制器和使用其他Bean一樣,可以充分利用依賴注入和Spring AOP。在HomePageController中,依賴注入用在了注入一個RantService.HomePageController
引入ModelAndView:控制器執行方法都必須返回一個ModelAndView。因此需要理解這個重要的類是如何工作的。正如它的名字表述的,ModelAndView保存了視圖以及視圖顯示的模型數據.在HomePageController里,ModelAndView對象應按照下面的方式構建: new ModeAndView("home","rants",recentRants); 構造器的第一個參數是視圖組件(用于顯示空氣器的輸出)的邏輯名稱。這里,視圖的邏輯名稱是home。視圖解析器會使用這個名稱查找實際的View對象 。 后兩個參數分別表示傳遞給視圖的模型對象。這個兩個參數是一個名字值對。第二個參數就是第三個參數所表示的模型對象名稱。
配置控制器Bean:現在HomePageController已經寫好了,你必須將其配置到DispatcherServlet的上下文配置文件中(對于RaodRantz應用程序就是roadrantz-servlet.xml文件)。下面這段XML代碼定義了HomePageController:
<bean name="/home.htm" class="com.roadrantz.mvc.HomePageController">
<property name="rantService" ref="rantService" />
</bean> 有件事讓你感到奇怪,不是為HomePageController Bean設置id屬性,而是設置name屬性。并且更奇怪的是設定的不是一個真實名字,而是給它設置一個URL"/home.htm"。這里name屬性承擔了兩個責任,即定義Bean的名字也定義需要使用這個控制器處理的URL樣式。由于URL樣式含有XMLid屬性中的非法字符--特別是斜杠(/),所以使用name屬性,而不使用id。只要進入DispatcherServlet的請求是以"/home.htm"結尾的,DispatcherServlet就會分給HomePageController來處理,注意這個Bean的name屬性使用的是URL樣式的唯一原因是我們還沒有配置處理映射Bean。DispatcherServlet使用的默認處理器映射是BeanNameUrlHandlerMapping,它使用URL樣式的名字。
聲明一個視圖解析器:返回給ModelAndView對象的其中一個值是邏輯視圖名稱。然而這個邏輯視圖名稱并不是直接引用特定的JSP的,而是用于間接的表達使用哪一個JSP。為了幫助Spring MVC了解使用哪一個JSP,你需要在roadrantz-servlet.xml中聲明另一個Bean:一個視圖解析器。簡單來看,視圖解析器的工作就是將視圖的名稱返回到ModelAndView中并將其映射到一個視圖上。在HomePageController中,我們需要一個視圖解析器將"home"解析成一個JSP文件來呈現主頁。Spring MVC帶來了很多可供選擇的視圖解析器。對于使用JSP渲染視圖來說,再也沒有比InteralResourceViewResolver更簡單的了:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>由于HomePageController返回的ModelAndView中的視圖名是home,InternalResourceViewResolver將在/WEB-INF/jsp/home.jsp處查找視圖。
-
-
將請求映射到控制器:當請求到達DispatcherServlet時,需要一些目錄來指明請求應該如何分配。處理器映射可以幫助DispatcherServlet了解請求應該被發送給哪個控制器。我們依賴DispatcherServlet默認使用的BeanNameUrlHandMapping。BeanNameUrlHandMapping很容易上手,但是 它無法滿足所有情況。所幸,Spring MVC提供了幾種可以選擇的處理器映射實現。Spring MVC中所有的處理器映射都實現了接口org.springframework.web.servlet.HandlerMapping (見PPT4)
-
使用SimpleUrlHandlerMapping:它可能是最直接的Spring處理器映射。它允許你將URL樣式直接映射到控制器,而且不需要以待定的方式命名你的Bean。例如:
<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/home.htm">homePageController</prop>
<prop key="/rantsForVehicle.htm">rantsForVehicleControllerRss</prop>
<prop key="/rantsForVehicle.rss">rantsForVehicleControllerRss</prop>
</props>
</property>
</bean>
<props>元素的key屬性是URL樣式。和BeanNameUrlHandlerMapping一樣,所有的URL樣式和DispatcherServlet的<servlet-mapping>一一對應。<prop>的值是處理這個URL的控制器Bean的名字。
使用ControllerClassNameHandlerMapping:很多時候,你會發現控制器映射的URL樣式都與控制器類名很相似。例如,在RoadRantz應用程序中,我們將rantForVehicle.htm映射到rantForVehicleController,將rantsForDay.htm映射到RantsForDayController。在這些例子中,URL樣式與控制器的類名相同,只不過去掉了Controller部分,加上了.htm部分。這種樣式就好像對映射設置了特定的默認值,而不需要明確地進行映射。這大概就是ControllerClassNameHandlerMapping所做的工作:
<bean id="urlMapping" class="org.springframework.web.servlet.mvc.ControllerClassNameHandlerMapping" />
這樣就可以通知Spring的DispatcherServlet將URL樣式按照簡單的約定映射到控制器。Spring不必明確的為每個控制器映射URL樣式,而是可以根據控制器的類名自動的將控制器映射到URL樣式上。 為了簡單起見,要想生成URL樣式,控制器類名中的Controller部分會被去掉(如果這部分存在),剩下的文本會變成小寫字母,并且在最前面加上一個反斜杠/,在最后加上".htm"。
使用CommonsPathMapHandlerMapping元數據映射控制器:它是根據控制器源代碼中的元數據決定如何進行URL映射。這個元數據特定是一個org.springframework.web.servlet.handler.commonsattributes.PathMap屬性,是用Jakarta Commons Attributes編譯器編譯到控制器中。 要使用它,只要像下面這樣簡單地在上下文控制文件中定義一個Bean就可以了:
<bean id="urlMapping" class="org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlerMapping" />
控制器源代碼注釋標簽中的PathMap屬性聲明了這個控制處理的URL樣式。例如,為了將HomePageController映射到"/home.htm",HomePageController的標簽是這樣的:
/**
* @@org.springframework.web.servlet.handler.commonsattributes.PathMap("/home.htm")
*/
public class HomePageController extends AbstractController {
......
} 最后,編譯的時候需要將Commons Attributes編譯器放進來,這樣標簽PathMap 才能被編譯到應用代碼中。要詳細了解如何在Ant 或Maven中設置Commons Attributes編譯器,請參考Commons Attributes 主頁(http://jakarta.apache.org/commons/attributes)。
使用多映射處理器:所有的處理器映射類都實現了Spring的Ordered接口。這意味著你可以在應用系統中聲明多個處理器映射,并且設置哪個相對另一個有優先權。例如,假設想在一個應用系統中并排使用BeanNameUrlHandlerMapping和SimpleUrlHandlerMapping。你需要像下面這樣聲明處理器映射Bean:
<bean id="beanNameUrlMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="order">
<value>1</value>
</property>
</bean>
<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="order"><value>0</value> </property>
<property name="mappings">
...
</property>
</bean>
注意,order屬性的值越小,優先級越高。
用控制器處理請求:如果DispatcherServlet是Spring MVC的心臟,那么控制器就是它的大腦。Spring控制器與Struts/web work action相比有一個重要的區別。這就是,相比Struts或Web Work比較平坦的Action層次,Spring提供了豐富的控制器層(見PPT5)在控制器層次的最上面是接口Controller,任何實現這個接口的類都可以用來處理Spring MVC框架傳遞過來的請求。要創建自己的控制器,你必須要做的就是實現這個接口。Spring讓你選擇最適合自己需要的控制器(見PPT6)Spring控制器可以歸為6種類型,沿著表格向下,功能越來越多,除了ThrowawayController,順著控制器類層次向下,每個控制器都是建立在它上面的控制器基礎之上的。
-
處理命令:當控制器需要根據參數執行工作時,應該繼承命令控制器,如AbstractCommandController(見PPT7)這個控制器會自動將參數綁定到命令對象中,并且提供了插入驗證器的鉤子,確保參數合法性。下面顯示了RantsForVehicleController,一個用于顯示特定車輛已有投訴列表的命令控制器。
public class RantsForVehicleController extends AbstractCommandController{
public RantsForVehicleController() {
setcommandClass(Vehicle.class);
setCommandName("vehicle");
}
protected ModelAndView handle(HttpServletRequest request,HttpServletResponse,Object command,
BindException errors) throws Exception {
Vehicle vehicle = (Vehicle) command;
List vehicleRants = rantService.getRantsForVehicle(vehicle);
Map model =errors.getModel();
model.put("rants", rantService.getRantsForVehicle(vehicle));
model.put("vehicle", vehicle);
return new ModelAndView("vehicleRants",model);
}
private RantService rantService;
public void setRantService(RantService rantService) {
this.rantService = rantService;
}
}
一個命令對象是一個為了簡化對請求參數訪問而設計的Bean。一個Spring命令對象是一個POJO,它不需要實現任何Spring的特定類。你需要在roadrantz-servlet.xml文件中注冊RantsForVehicleController
<bean id="rantsForVehicleController" class="com.roadrantz.mvc.RantsForVehicleController">
<property name="rantService" ref="rantService"/>
</bean>
處理表單提交:表單控制器比命令控制器前進了一步,(見PPT8)它在接收到HTTP GET請求的時候顯示一個表單,接收到一個HTTP POST請求的時候處理這個表單。另外,在處理過程中如果發生錯誤的話,這個控制器會知道重新顯示這個表單,這樣用戶就可以修改錯誤,重新提交。為了顯示表單控制器是如何工作的,考慮以下程序中的AddRantFormController。
public class AddRantFormController extends SimpleFormController {
private static final String[] ALL_STATES = {"AL","AK","AZ"};
public AddRantFormController() {
setCommandClass(Rant.class);
setCommandName("rant");
}
protected Object formBackingObject(HttpServletRequest request) throws Exception {
Rant rantForm = (Rant)super.formBackingObject(request);
rantForm.setVehicle(new Vehicle());
}
protected Map referenceDate(HttpServletRequest request) throws Exception{
Map referenceData = new HashMap();
referenceDate.put("states",ALL_STATES);
return referenceData;
}
protected ModelAndView onSumbit(Object command,BindException bindException)throws Exception{
Rant rant = (Rant)command;
rantService.addRant(rant);
return new ModelAndView(getSuccessView());
}
private RantService rantService;
public void setRantService(RantService rantService) {
this.rantService = rantService;
}
}雖然referenceData()方法是可選的,但是如果需要為 顯示表單提供其他附加的信息,則可以使用這個方法。在正常的情況下,返回表單的命令對象一般是一個簡單的命令類實例。不過對于AddRantFormController,Rant實例不會完成這項工作。表單會使用內嵌的Vehicle屬性作為表單返回對象的一部分。因此,需要覆蓋fromBackingObject()方法,以便設置vehicle屬性。否則,在控制器視圖綁定state和plateNumber屬性時,會拋出NullPointerException異常。
那么控制器是如何知道顯示投訴輸入表單的。如果投訴輸入成功,用戶將到什么頁面也不是很清楚。唯一的線索是對getSuccessView()的調用結果會提交給ModelAndView。但是,提交成功的視圖又從哪里來呢?SimpleFormController被設計成盡量將視圖詳細信息放在控制器代碼之外。不是將ModelAndView對象硬編碼進來,而是像下面這樣在上下文配置文件中配置控制器:
<bean id="addRantController" class="com.roadrantz.mvc.AddRantFormController">
<property name="formView" value="addRant" />
<property name="successView" value="rantAdded" />
<property name="rantService" ref="rantService" />
</bean> formView屬性是控制器接收到HTTP GET請求或有任何錯誤發生時需要顯示的視圖的邏輯名。同樣,successView是提交的表單成功處理后要顯示的視圖的邏輯名。
驗證表單輸入:org.springframework.validation.Validator接口為Spring MVC提供了驗證功能,定義如下:
public interface Validator {
void validate(Object obj , Errors errors);
boolean supports(Class clazz);
}這個接口的實現必須驗證傳遞給validate()方法的對象的字段,用Errors對象駁回任何非法數據。supports()方法用于幫助Spring判斷該驗證器是否可以用于指定類。 以下是一個用于驗證Rant對象的Validator實現。
public class RantValidator implements Validator {
public boolean supports(Class clazz){
return clazz.equals(Rant.class);
}
public void validate(Object command, Errors errors) {
Rant rant = (Rant) command;
ValidationUtils.rejectIfEmpty(errors,"vehicle.state","required.state","State is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "rantText", "required.rantText","You must enter some rant text.");
validatePlateNumber(rant.getVehicle().getPlateNumber(),errors);
}
private static final String PLATE_REGEXP = "/[a-z0-9]{2,6}/i";
private void validatePlateNumber(String plateNumber,Errors errors) {
Per15Util per15Util = new Per15Util();
if(!per15Util.match(PLATE_REGEXP,plateNumber)){
errors.reject("invalid.plateNumber","Invalid license plate number.");
}
}
}還有一件事情要做,就是讓AddRantFormController使用RantValidator。你可以將一個RantValidator Bean裝配到AddRantFormController Bean中:
<bean id="addRantController" class="com.roadrantz.mvc.AddRantFormController">
<property name="formView" value="addRant"/>
<property name="successView" value="rantAdded"/>
<property name="rantService" ref="rantService"/>
<property name="validator">
<bean class="com.roadrantz.mvc.RantValidator"></bean>
</property>
</bean>通過實現Validation接口,可以通過程序完全控制應用程序命令對象的驗證。如果需要復雜的驗證和特殊的邏輯,利用這項功能將會十分方便。對于簡單的情況,例如保證填入需要的字段并按照基本格式,編寫自己的Validator接口就有點麻煩了。聲明性驗證是一個不錯的選擇。
利用命令驗證器進行驗證:在我們深入研究用于實現聲明性Validator的Spring JavaDoc之前,需要知道Spring并不提供這樣的驗證器。Spring沒有提供任何Validator接口的實現,而是將這個任務留給了程序員。 不過Spring Modules項目(http://springmodules.dev.java.net)是Spring的一個姊妹項目,提供了幾個對Spring的擴展。其中一個是使用Jakarta Commons Validator(http://jakarta.apache.org/commons/validator)提供的聲明性驗證的驗證模塊。
要使用驗證模塊,首先需要添加springmodules-validator,jar 如果使用Ant建立應用程序,需要下載Spring Modules發行包,找到spring-modules-0.6.jar文件,將這個JAR添加到<war>任務的<lib>中。如果使用Maven2建立應用程序,需要在pom.xml中添加下面的<dependency>:
<dependency>
<groupId>org.springmodules</groupId>
<artifactId>springmodules-validation</artifactId>
<version>0.6</version>
<scope>compile</scope>
</dependency>
另外,還需要將Jakarta Commons Validator JAR添加到應用程序的classpath中,在Maven中,按如下所示:
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.1.4</version>
<scope>compile</scope>
</dependency>
Spring Module提供的Validator實現的名稱為DefaultBeanValidator。DefaultBeanValidator需要按下面的方式配置在roadrantz-servlet.xml中:
<bean id="beanValidator" class="org.springmodules.commons.validator.DefaultBeanValidator">
<property name="validatorFactory" ref="validatorFactory"></property>
</bean>
DefaultBeanValidator并不做任何實際的驗證工作,而是委派Commons Validator來驗證字段的值。validatorFactory Bean需要使用下面的XML進行聲明:
<bean id="validatorFactory" class="org.springmodules.commons.validator.DefaultValidatorFactory">
<property name="validationConfigLocations">
<list>
<value>WEB-INF/validator-rules.xml</value>
<value>WEB-INF/validator.xml</value>
</list>
</property>
</bean> validator-rules.xml文件包含了一組預定義的驗證規則,可以應用于一般的驗證需求。這個文件被包含在Commons Validator發行包中,因此,你不需要自己編寫--值需要簡單的將其添加到應用程序的WEB-INF目錄中。(PPT9)中列出了validator-rules.xml中的所有驗證規則。另一個文件validation.xml定義了應用程序制定的驗證規則,可有直接應用于RoadRantz應用程序。下面展示了應用到RoadRantz的validation.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1
//EN" "http://jakarta.apache.org/commons/dtds/validator_1_1.dtd" >
<form-validation>
<formet>
<form>
<field property="rantText" depends="required">
<arg0 key="required.rantText"/>
</field>
<field property="vehicle.plateNumber" depends="required,mask">
<arg0 key="invalid.plateNumber"/>
<var>
<var-name>mask</var-name>
<var-value>^[0-9A-Za-z]{2,6}$</var-value>
</var>
</field>
</form>
</formet>
</form-validation>
最后一件要做的事情是改變控制器的聲明,注入新的聲明性Validate實現:
<bean id="addRantController" class="com.roadrantz.mvc.AddRantFormController">
<property name="formView" value="addRant" />
<property name="successView" value="rantAdded" />
<property name="rantService" value="rantService" />
<property name="validator" value="beanValidator" />
</bean>
使用SimpleFormController的一個基本的原因是表單只有一頁。
用向導處理復雜表單:AbstractWizardFormController是Spring提供的功能最強大的控制器(見PPT10)它是一種特殊類型的表單控制器,它將多個頁面中的表單數據聚集到一個用于處理的命令對象中。
-
創建一個基本的向導控制器:構建一個向導控制器,它必須繼承AbstractWizardFormController類,如下:
public class MotoristRegistrationController extends AbstractWizardFormController {
public MotoristRegistrationController() {
setCommandClass(Motorist.class);
setCommandName("motorist");
}
protected Object formBackingObject(HttpServletRequest request) throws Exception{
Motorist formMotorist = new Motorist();
List<Vehicle> vehicles = new ArrayList<Vehicle>();
vehicles.add(new Vehicle());
formMotorist.setVehicles(vehicles);
return formMotorist;
}
protected Map referenceData(HttpServletRequest request,Object command,Errors errors,int page)
throws Exception{
Motorist motorist = (motorist) command;
Map refData = new HashMap();
if(page==1 && request.getParameter("_target1")!= null){
refData.put("nextVehicle", motorist.getVehicles().size()-1);
}
return refData;
}
protected void postProcessPage(HttpServletRequest request,Object command,Errors errors,int page)throws Exception {
Motorist motorist = (Motorist)command;
if(page==1 && request.getParameter("_target1")!= null){
motorist.getVehicles().add(new Vehicle());
}
}
protected ModelAndView processFinish(HttpServletRequest request,HttpServletResponse response,
Object command,BindException errors)throws Exception {
Motorist motorist = (motorist) command;
// the last Vehicle is always blank...remove it
motorist.getVehicles().remove(motorist.getVehicles().size()-1);
rantService.addMotorist(motorist);
return new ModelAndView(getSuccessView(),"motorist",motorist);
}
//inject
private RantService rantService;
public void setRantService(RantService rantService) {
this.rantService = rantService;
}
//returns the last page as the success view
private String getSuccessView() {
return getPages()[getPages().length-1];
}
}
AbstractWizardFormController的唯一一個必須實現的方法是processFinish()。在用戶完成表單后(一般是點擊完成按鈕),這個方法被調用,完成整個表單。 但是AbstractWizardFormController是如何知道哪些頁面構成了整個表單呢?
<bean id="registerMotoristController" class="com.roadrantz.mvc.MotoristRegistrationController">
<property name="rantService" ref="rantService" />
<property name="pages">
<list>
<value>motoristDetailForm</value>
<value>motoristVehicleForm</value>
<value>motoristConfirmation</value>
<value>redirect:home.htm</value>
</list>
</property>
</bean>這樣向導就知道了哪些頁面構成了表單,一個視圖邏輯名列表設置給了pages屬性。這些名字最終被視圖解析器解析成了一個個視圖對象。但現在,只要假設這些名字會被解析成JSP文件名就可以了。
分步顯示表單頁面:任何向導控制器顯示的第一個頁面都是pages屬性中列表的第一個頁面。AbstractWizardFormController詢問它的getTargetPage()方法。這個方法返回一個整數,它是pages屬性中設置的頁面列表的索引值(以0為基數)。getTargetPage()方法的默認實現是根據請求中的一個參數來決定下一步是哪個頁面,這個參數以"_target"開頭,以數字結尾。知道了getTargetPage()的工作原理有助于你知道如何在向導HTML頁面中構造下一步和上一步按鈕。例如,假設用戶在"motoristVehicleForm"頁面上(索引=1)。要在這個頁面上創建下一步和上一步按鈕,你要做就是創建提交按鈕,它的名字是以"_target"開頭:
<form>
...
<input type="submit" value="Back" name="_target0">
<input type="submit" value="Next" name="_target2">
</form>getTargetPage()方法的默認實現對于大多數項目已經夠用了。然后,如果你喜歡為你的向導定義自己的工作流程的話,你可以覆寫這個方法。
完成向導:還有一個特殊請求的參數"_finish",和"_targetX"參數一樣,它可以被用于在頁面上創建一個結束按鈕:
<form method="POST" action="feedback.htm">
...
<input type="submit" value="Finish" name="_finish">
</form>
當AbstractWizardFormController看到請求中的"_finish"參數時,它會將控制權交給processFinish()方法,讓它對表單做最后的處理。與其他表單控制器不同,AbstractWizardFormController不提供用于設置成功視圖頁面的內容。因此,我們在MotoristRegisttrationController中添加了一個getSuccessView()方法返回頁面列表中的最后一個頁面。所以,當表單已完成的方式提交時,processFinish()方法會返回一個帶有視圖的ModelAndView,這個視圖就是頁面列表中的最后一個視圖。
取消向導:如何用戶完成了部分注冊,決定不再繼續完成,除了選擇直接關閉瀏覽器外,還有另一種選擇,你可以在表單上添加一個取消按鈕。
<form method="POST" action="feedback.htm">
...
<input type="submit" value="Cancel" name="_cancel">
</form>
取消按鈕以"_cancel"作為它的名字,當用戶按下取消按鈕,瀏覽器將一個叫做"_cancel"的參數放到請求中。AbstractWizardFormController接收到這個參數,將控制權交給processCancel()方法。 默認情況下此方法會拋出異常,表示取消操作是不被支持的,我們要覆寫這個方法,將用戶帶到你想讓他們點擊取消時看到的頁面。下面processCancel()方法的實現將用戶帶領到成功視圖。
protected ModelAndView processCancel(HttpServletRequest request, HttpServletResponse response, Object command, BindException bindException) throws Exception {
return new ModelAndView(getSucessView()) ;
}
每次驗證一個向導表單:使用其他類型的命令控制器,命令對象只裝載一個次。但使用向導控制器,用戶每完成向導頁面中的一步,都會有一個命令對象設置進來。使用向導,只做一次驗證是不可行的,太早的話,找到的驗證問題可能是由于用戶沒有完成向導而導致的。太晚的話,在完成按鈕被按下后再做檢查就太遲了,因為發現的問題可能越過了多個頁面(用戶該回到哪個頁面呢?) 向導控制器在每個頁面驗證一次命令對象,不是只驗證一次。這是通過每次頁面跳轉時調用validatePage()方法實現的。validatePage()方法的默認實現是空的(也就是沒有驗證),但是你可以覆寫這個方法,做出自己的判斷。 假設在motoristDetailForm頁面,詢問用戶的郵件地址,這個字段是可選的,但是如果輸入了值,必須輸入一個合法的E-mail地址。下面的validatePage()方法展示了用戶從motoristDetailForm頁面跳轉出來的時候如何驗證E-mail地址。
protected void validatePage(Object command, Errors errors, int page ) {
Motorist motorist = (Motorist) command;
Motorist Validator validator = (MotoristValidator) getValidator();
if(page == 0){
validator.validateEmail(motorist.getEmail() , errors );
}
} 這里可以直接在validatePage()方法中檢查E-mail。然后,向導一般有好幾項字段需要驗證。如果這樣的話,validatePage()方法會變得很笨拙。我們建議你將驗證任務委托給控制器的驗證器對象的字段級驗證方法,就像我們在這里調用MotoristValidator的validateEmail()方法一樣。 這意味著當你配置控制器的時候,你要設置validator屬性:
<bean id="registerMotoristController" class="com.roadrantz.mvc.MotoristRegistrationController">
<property name="rantService" ref="rantService" />
<property name="pages">
<list>
<value>motoristDetailForm</value>
<value>motoristVehicleForm</value>
<value>motoristConfirmation</value>
<value>redirect:home.htm</value>
</list>
</property>
<property name="validator">
<bean class="com.roadrantz.mvc.MotoristValidator" />
</property>
</bean>
一個需要注意的重要事項是,不像其他命令控制器,向導控制器從不調用它們的驗證器對象的標準validate()方法。這是因為validate()方法驗證整個命令對象,然而在向導中命令對象將在每個頁面驗證一次。
使用一次性控制器:一次性控制器比其他控制器簡單很多,ThrowawayController接口可以證明:
public interface ThrowawayController {
ModelAndView execute() throws Exception ;
}
ThrowawayController接口和Controller接口不在同一個體系里。一次性控制器自己作為自己作為自己的命令對象,而不是通過一個HttpServletRequest或一個命令對象獲得參數。與WebWork Action相似,都是以同樣的方式工作。我們將RantsForController實現為ThrowawayController:
public class RantsForDayController implements ThrowawayController{
private Day day;
public ModelAndView execute() throws Exception {
List<Rant> dayRants = rantService.getRantsForDay(day);
return new ModelAndView("dayRants","rants",dayRants);
}
public void setDay(Date day){
this.day = day;
}
private RantService rantSrvice;
public void setRantSrvice(RantService rantSrvice) {
this.rantSrvice = rantSrvice;
}
} 你必須在DispatcherServlet的上下文配置文件中聲明一次性控制器。只有一點很小的不同:
<bean id="rantsForDayController" class="com.roadrantz.mvc.RantsForDayController" scope="prototype">
<property name="rantService" ref="rantService">
</bean> scope屬性已經被設置為prototype。這就是一次性控制器獲取它們名字的地方。默認情況下,所有的Bean都是Singleton。 但是因為ThrowawayContoller和Controller不處于同一繼承層次,DispatcherServlet不知道如何通知ThrowawayController。所以你必須告訴DispathcerServlet使用一種不同的 處理適配器。確切的說,你必須像下面這樣配置ThrowawayControllerHandlerAdapter:
<bean id="throwawayHandler" class="org.springframework.web.servlet.mvc.throwaway.ThrowawayControllerHandlerAdapter" />
如果應用程序既使用了常規控制器又使用了一次性控制器,你還應該按如下方式聲明SimpleControllerHandleAdapter:
<bean id="simpleHandler" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
聲明兩處理器適配器使你可以在用一個應用程序中混合使用兩種類型的控制器。
-
處理異常:當異常從控制器中跑出來時,SimpleMappingExceptionResolver負責營救。
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">friendlyError</prop>
</props>
</property>
</bean>
exceptionMappings屬性取得一個java.util.Properties,它映射了異常類名和邏輯視圖名。在本例中,基礎異常類被映射到邏輯名為friendlyError的視圖上,這樣如果有任何異常拋出的話,用戶不會在瀏覽器中看到一串晦澀的堆棧跟蹤信息。
-
-- 學海無涯