javascript
Spring MVC的表单控制器——SimpleFormController .
http://blog.csdn.net/rj042/article/details/6907274
?
年7月微軟MVP申請開始啦!?????????? CSDN十大風(fēng)云博客專欄評選結(jié)果公布!Spring MVC的表單控制器——SimpleFormController
分類: java 2011-10-26 15:09 264人閱讀 評論(2) 收藏 舉報(bào) 概述大多數(shù)Web應(yīng)用都會遇到需要填寫表單的頁面,當(dāng)表單提交成功后,表單的數(shù)據(jù)被傳送給Web服務(wù)器中處理。處理成功后導(dǎo)向到一個(gè)成功頁面,如果操作失敗則導(dǎo)向到一個(gè)錯(cuò)誤報(bào)告頁面。此外,在表單數(shù)據(jù)處理之前還會進(jìn)行表單數(shù)據(jù)的驗(yàn)證,保證客戶端提交的表單數(shù)據(jù)是合法有效的,如果數(shù)據(jù)不合法,請求返回到原表單頁面中,以便用戶根據(jù)錯(cuò)誤信息進(jìn)行修改。
假設(shè)你想成為論壇的用戶時(shí),必須填寫一張用戶注冊表單,這可能包括用戶名、密碼、Email等注冊信息。用戶提交表單后,服務(wù)器驗(yàn)證注冊數(shù)據(jù)合法性,如果你填寫的信息是合法的,系統(tǒng)將在數(shù)據(jù)庫中創(chuàng)建一個(gè)新用戶,用戶注冊就完成了。
用戶注冊表單控制器
通過擴(kuò)展SimpleFormController可以按照標(biāo)準(zhǔn)的表單處理流程處理用戶注冊的請求,UserRegisterController用于負(fù)責(zé)處理用戶注冊的請求:
代碼清單 1 UserRegisterController
package com.baobaotao.web.user;
import org.springframework.web.servlet.mvc.SimpleFormController;
import com.baobaotao.domain.User;
import com.baobaotao.service.BbtForum;
public class UserRegisterController extends SimpleFormController {
private BbtForum bbtForum;
public UserRegisterController(){
setCommandClass(User.class); ①指定命令對象(這時(shí)也稱表單對象)的類型
}
public void setBbtForum(BbtForum bbtForum) {
this.bbtForum = bbtForum;
}
②通過該方法處理表單提交請求
protected void doSubmitAction(Object command) throws Exception {
User user = (User) command;
bbtForum.registerUser(user);
}
} 在①處指定表單對象的類型,以便控制器自動(dòng)將表單數(shù)據(jù)綁定到表單對象中,你也可以直接在配置文件中通過commandClass屬性進(jìn)行設(shè)置:
<property name="commandClass" value=" com.baobaotao.domain.User"/>
在②處復(fù)寫了doSubmitAction()方法,在該方法內(nèi)部通過調(diào)用業(yè)務(wù)層的bbtForum保存表單對象,創(chuàng)建新用戶。當(dāng)你不需要返回模型對象給成功頁面時(shí),復(fù)寫doSubmitAction()方法是最佳的選擇,因?yàn)樵摲椒]有返回值。如果需要返回模型對象給成功頁面,那么就必須復(fù)寫表單控制器的onSubmit ()方法。用戶注冊成功后,我們一般需要在成功頁面中根據(jù)用戶信息提供個(gè)性化的內(nèi)容,這就要求控制器返回相應(yīng)的User模型對象,此時(shí)需要在UserRegisterController中復(fù)寫onSubmit ()方法:
…
protected ModelAndView onSubmit (Object command, BindException errors)
throws Exception {
User user = (User) command;
bbtForum.registerUser(user);
return new ModelAndView(getSuccessView(), "user", user);①user中包含注冊用戶的信息
}
…
當(dāng)你復(fù)寫onSubmit ()方法后,doSubmitAction()方法就不會得到執(zhí)行了, onSubmit ()方法比doSubmitAction()方法具有更高的調(diào)用優(yōu)先級,所以你只要根據(jù)要求復(fù)寫兩者中的一個(gè)方法就可以了。在onSubmit ()中返回的ModelAndView的邏輯視圖名應(yīng)該是通過表單控制器的successView屬性指定而不應(yīng)該硬編碼,所以在①處我們通過getSuccessView()獲取這個(gè)配置值。
表單控制器的工作流程從表單頁面提交開始,處理成功后轉(zhuǎn)向成功頁面,這個(gè)流程涉及到兩個(gè)視圖:表單頁面和成功頁面,這需要在表單控制器中通過屬性進(jìn)行定義:
<bean name="/registerUser.html" class="com.baobaotao.web.user.UserRegisterController">
<property name="bbtForum" ref="bbtForum" />
<property name="formView" value="register" /> ①表單錄入頁面(邏輯視圖名,下同)
<property name="successView" value="registerSuccess" /> ②成功頁面
</bean>
通過formView屬性指定表單錄入頁面對應(yīng)的邏輯視圖名,而successView屬性表示成功頁面的視圖邏輯名。通過代碼清單 2前后綴視圖解析器的處理,它們將分別對應(yīng)WEB-INF/jsp/register.jsp和WEB-INF/jsp/registerSuccess.jsp的JSP頁面。
代碼清單 2 前后綴視圖解析器
…
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix"> ①前綴
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix"> ②后綴
<value>.jsp</value>
</property>
</bean>
一般情況下表單錄入頁面需要通過Spring表單標(biāo)簽綁定表單對象,以便根據(jù)表單對象初始值生成表單頁面,在校驗(yàn)失敗后能夠重現(xiàn)提交前的表單數(shù)據(jù)。讓我們看看這個(gè)register.jsp用戶注冊頁面的內(nèi)容:
代碼清單 3 register.jsp:用戶注冊頁面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>寶寶淘論壇用戶注冊</title>
</head>
<body>
①Spring MVC表單標(biāo)簽,可以直接和/registerUser.html控制器
綁定(fromView),無需通過action指定提交的目標(biāo)地址。
<form:form>
用戶名:<form:input path="userName" />
<br>
密 碼:<form:password path="password" />
<br>
Email:<form:input path="email" />
<br>
<input type="submit" value="注冊" />
<input type="reset" value="重置" />
</form:form>
</body>
</html>
在①處,應(yīng)用Spring的表單標(biāo)簽定義了一個(gè)能夠和表單對象綁定的頁面表單。和Struts不同的是作為表單標(biāo)簽的<form:form>元素?zé)o需設(shè)定提交地址(在Struts中必須指定表單標(biāo)簽的action屬性),Spring MVC能夠自動(dòng)根據(jù)控制器的formView屬性獲知該表單頁面的提交地址。使用過Struts Action開發(fā)表單提交功能的讀者也許會知道開發(fā)Struts處理表單功能是比較麻煩,因?yàn)榭赡軙榱碎_發(fā)一個(gè)表單設(shè)計(jì)多個(gè)Action:一個(gè)用戶初始化表單,另一個(gè)用于提交表單。而Spring的SimpleFormController的高明之處在于,它已經(jīng)將表單處理工作流程編制到控制器中,我們僅需要在子類中復(fù)寫開放出方法就可以充分享受預(yù)定義工作流程的好處。我們應(yīng)該如何有選擇地覆蓋父類方法,以便正確地影響表單工作流程呢?這回答這個(gè)問題需要對SimpleFormController的工作流程有一個(gè)詳細(xì)的了解。
表單控制器完整工作流程
使用SimpleFormController時(shí),你無需為初始化表單編寫額外的控制器,當(dāng)你通過GET請求訪問表單控制器時(shí),表單控制器自動(dòng)將請求導(dǎo)向到表單錄入頁面。而當(dāng)你通過POST請求訪問表單控制器時(shí),表單控制器執(zhí)行表單提交的業(yè)務(wù),根據(jù)處理成功與否,或?qū)虻匠晒撁?#xff0c;或?qū)虻奖韱武浫腠撁?#xff08;當(dāng)發(fā)生異常時(shí)導(dǎo)向到錯(cuò)誤頁面)。
SimpleFormController的工作流程比較復(fù)雜,我們通過下面的流程圖對此進(jìn)行描述:
1.當(dāng)表單控制器接收到GET請求時(shí),它調(diào)用formBackingObject()方法,創(chuàng)建表單對象。該方法可以被子類覆蓋,對于編輯操作的表單來說,你可以通過該方法從數(shù)據(jù)庫中加載表單對象,當(dāng)表單頁面顯示時(shí),表單顯示出待編輯的數(shù)據(jù)了;
2.表單對象和頁面表單數(shù)據(jù)之間需要通過屬性編輯器實(shí)現(xiàn)雙向轉(zhuǎn)化,對于非基本數(shù)據(jù)類型或String類型的屬性來說,你可能需要注冊一些自定義編輯器。你可以通過覆蓋initBinder()方法,通過調(diào)用binder.registerCustomEditor()的方法注冊編輯器;
3.表單對象通過bindOnNewForm屬性(可以通過配置設(shè)置,默認(rèn)為false)判斷是否需要將GET請求參數(shù)綁定到formBackingObject()方法創(chuàng)建的表單對象中。如果bindOnNewForm為true,執(zhí)行綁定操作,在綁定完成后,還將調(diào)用onBindOnNewForm()回調(diào)方法(子類可以提供具體實(shí)現(xiàn))。否則到下一步。不過一般情況下,GET請求參數(shù)是用于加載等編輯表單對象的ID值,如topicId、forumId等,一般無需進(jìn)行綁定;
4.調(diào)用referenceData()方法(子類可提供具體實(shí)現(xiàn))準(zhǔn)備一些關(guān)聯(lián)的數(shù)據(jù),如性別下拉框數(shù)據(jù),學(xué)歷下拉框數(shù)據(jù)等。一般采用ModelMap創(chuàng)建視圖業(yè)務(wù)中需要用到的請求屬性數(shù)據(jù),鍵為屬性名,值為屬性值,如ModelMap("param1", "paramValue1");
5.使用控制器formView定義的視圖渲染表單對象;
6.用戶填寫或更改表單后,提交表單,向表單控制器發(fā)起一個(gè)POST請求;
7.接收到POST請求時(shí),表單控制器知道這是一個(gè)表單數(shù)據(jù)提交的操作,所以啟動(dòng)表單提交處理流程;
8.首先通過sessionForm屬性判斷表單控制器是否啟用了Session。如果啟用了Session,直接從Session中取出原表單對象,否則再次調(diào)用formBackingObject()方法構(gòu)造出一個(gè)表單對象。sessionForm默認(rèn)為false,可以通過配置進(jìn)行調(diào)整,啟用Session可能提高運(yùn)行性能,但會占用一定的內(nèi)存;
9.將POST請求參數(shù)填充到表單對象中;
10.調(diào)用onBind()方法,該方法允許你在表單填充完成后,合法性校驗(yàn)之前執(zhí)行一些特定的操作;
11.如果validateOnBinding屬性設(shè)置為true,注冊在控制器中的校驗(yàn)器開始工作,對表單對象的屬性值執(zhí)行合法性校驗(yàn)。如果有合法性錯(cuò)誤,將被注冊到Errors對象中(關(guān)于如何注冊校驗(yàn)器,我們將稍后介紹);
12.調(diào)用onBindAndValidate()方法,該方法允許你在數(shù)據(jù)綁定及合法性校驗(yàn)后,執(zhí)行一些額外的自定義操作,你也可以在這里,執(zhí)行一些額外的合法性校驗(yàn);
13.調(diào)用processFormSubmission()方法處理提交任務(wù),該方法內(nèi)部又包含后續(xù)幾步工作;
14.判斷方法入?yún)魅雃rrors是否包含錯(cuò)誤,如果包含錯(cuò)誤返回到formView對應(yīng)的表單頁面中,否則到下一步;
15.通過isFormChangeRequest()方法(默認(rèn)為false)判斷請求是否為表單更改請求,如果為true,調(diào)用onFormChange()方法,然后返回到formView對應(yīng)的表單頁面,否則到下一步;
16.如果子類覆蓋了onSubmit()方法,執(zhí)行之,否則執(zhí)行子類的doSubmitAction()方法。通過這兩者之一完成業(yè)務(wù)的處理,然后返回successView屬性指定的成功頁面。
我們可以按照以上表單控制器的工作流程,根據(jù)業(yè)務(wù)需要有選擇地覆蓋一些父類的方法完成特定的操作。假設(shè)我們在開發(fā)一個(gè)編輯用戶信息的功能,在展現(xiàn)表單前需要先從數(shù)據(jù)庫中查詢出用戶信息并在更改表單中展現(xiàn),這時(shí),我們僅需覆蓋formBackingObject()方法,執(zhí)行查詢操作就可以了,其代碼形如下所示:
…
① 根據(jù)請求參數(shù)從數(shù)據(jù)庫中查詢出User對象,作為更新用戶表單的初始值
protected Object formBackingObject(HttpServletRequest request) throws Exception {
int userId = ServletRequestUtils.getIntParameter(request, "userId",-1);
User user = bbtForum.getUser(userId);
user.setUserName("user1");
return user;
}
…
ServletRequestUtils是Spring 2.0新增的工具類,可以方便地按類型獲取請求參數(shù)的值,它位于org.springframework.web.bind包中。
表單數(shù)據(jù)校驗(yàn)
當(dāng)UserRegisterController調(diào)用BbtForum#registerUser()方法注冊用戶時(shí),確保User對象數(shù)據(jù)的合法性是非常重要的,你不希望用戶的Email地址是非法的,用戶名不應(yīng)和已經(jīng)用戶名相同。
org.springframework.validation.Validator接口為Spring MVC提供了數(shù)據(jù)合法性校驗(yàn)功能,該接口有兩個(gè)方法,說明如下: boolean supports(Class clazz):判斷校驗(yàn)器是否支持指定的目標(biāo)對象,每一個(gè)校驗(yàn)器負(fù)責(zé)對一個(gè)表單類的對象進(jìn)行檢驗(yàn);
void validate(Object target, Errors errors):對target對象進(jìn)行合法性校驗(yàn),通過Errors返回校驗(yàn)錯(cuò)誤的結(jié)果。
下面,我們編寫一個(gè)負(fù)責(zé)對User對象進(jìn)行數(shù)據(jù)合法性校驗(yàn)的校驗(yàn)器,請看以下的代碼:
代碼清單 4 UserValidator:校驗(yàn)User對象值合法性
package com.baobaotao.domain.UserValidator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
public class UserValidator implements Validator {
private static final Pattern EMAIL_PATTERN = Pattern ①合法Email正則表達(dá)式
.compile("(?:w[-._w]*w@w[-._w]*w.w{2,3}$)");
public boolean supports(Class clazz) { ②該校驗(yàn)器支持的目標(biāo)類
return clazz.equals(User.class);
}
public void validate(Object target, Errors errors) { ③對目標(biāo)類對象進(jìn)行校驗(yàn),錯(cuò)誤記錄在errors中
User user = (User) target; ③-1 造型為User對象
③-2 通過Spring提供的校驗(yàn)工具類進(jìn)行簡單的規(guī)則校驗(yàn)
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
"required.username", "用戶名必須填寫");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password",
"required.password", "密碼不能為空");
validateEmail(user.getEmail(), errors); ③-3 校驗(yàn)Email格式
}
private void validateEmail(String email, Errors errors) {④Email合法性校驗(yàn)
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email",
"required.email", "Email不能為空");
Matcher m = EMAIL_PATTERN.matcher(email); ④-1 通過正則表達(dá)式校驗(yàn)Email格式
if (!m.matches()) {
errors.rejectValue("email", "invalid.email", "Email格式非法");
}
}
}
在②處,我們聲明該校驗(yàn)器支持的表單對象為User類,如果錯(cuò)誤地將UserValidator用于其它對象校驗(yàn),Spring MVC就會根據(jù)supports()方法駁回操作。
對于一般的空值校驗(yàn)來說,直接使用Spring提供的ValidationUtils校驗(yàn)工具類是最簡單的辦法(如③-2所示)。ValidationUtils的rejectIfEmptyOrWhitespace()、rejectIfEmpty()以及Errors的reject()、rejectValue()方法都擁有多個(gè)用于描述錯(cuò)誤的入?yún)?#xff0c;通過下圖進(jìn)行說明:
1)對應(yīng)字段:表示該錯(cuò)誤是對應(yīng)表單對象的哪一個(gè)字段,Spring MVC的錯(cuò)誤標(biāo)簽可以通過path屬性訪問該字段錯(cuò)誤消息;
2)錯(cuò)誤代碼:表示該錯(cuò)誤對應(yīng)資源文件中的鍵名,Spring MVC的錯(cuò)誤標(biāo)簽可以據(jù)此獲取資源文件中的對應(yīng)消息。如果希望實(shí)現(xiàn)錯(cuò)誤消息的國際化,你就必須通過錯(cuò)誤代碼指定錯(cuò)誤消息;
3)默認(rèn)消息:當(dāng)資源文件沒有對應(yīng)的錯(cuò)誤代碼時(shí),使用默認(rèn)消息作為錯(cuò)誤消息。
我們“驚訝地”發(fā)現(xiàn)入?yún)⒘斜聿]有包括需要校驗(yàn)的目標(biāo)表單對象,那如何對目標(biāo)表單對象實(shí)施校驗(yàn)?zāi)?#xff1f;原來目標(biāo)對象已經(jīng)包含在errors對象中,在校驗(yàn)方法內(nèi)部會從errors中取得目標(biāo)方法并施加校驗(yàn)。
在④處,我們通過正則表達(dá)式對Email格式進(jìn)行校驗(yàn)。我們直接使用JDK 1.4 java.util.regex包中提供的正則表達(dá)式工具類完成校驗(yàn)的工作。由于Email模式是固定的,為了提高性能,我們在①處用final static的方式定義了一個(gè)Email合法模式的Pattern對象。
編寫好UserValidator,我們需要將其裝配到UserRegisterController控制器中,其配置如下所示:
<bean name="/registerUser.html" class="com.baobaotao.web.user.UserRegisterController">
<property name="bbtForum" ref="bbtForum" />
<property name="formView" value="register" />
<property name="successView" value="registerSuccess" />
<property name="validator"> ①裝配校驗(yàn)器
<bean class="com.baobaotao.domain.UserValidator" />
</property>
</bean>
在①處我們通過validator指定了一個(gè)對User表單對象進(jìn)行校驗(yàn)的校驗(yàn)器,如果你有多個(gè)校驗(yàn)器類(很少見),可以通過validators屬性進(jìn)行指定。
我們通過UserValidator可以很好地完成User對象屬性值的格式檢查,可是仔細(xì)想想是否還存在遺漏呢?也許你已經(jīng)指出:userName不能和數(shù)據(jù)庫中已有用戶名重復(fù)!你當(dāng)然可以在UserValidator中通過注入業(yè)務(wù)對象完成userName重復(fù)性的校驗(yàn),但對于這種需要通過業(yè)務(wù)對象完成的校驗(yàn)操作,一種更好的方法是通過覆蓋控制器的onBindAndValidate()方法,直接在控制器中提供檢驗(yàn)。這帶來了一個(gè)好處,UserValidator無需和業(yè)務(wù)對象打交道,而UserRegisterController本身已經(jīng)擁有了業(yè)務(wù)對象的引用,所以調(diào)用業(yè)務(wù)對象執(zhí)行校驗(yàn)非常方便。下面的代碼展示了UserRegisterController中onBindAndValidate()的內(nèi)容:
代碼清單 5 UserRegisterController#onBindAndValidate()通過業(yè)務(wù)對象完成校驗(yàn)
package com.baobaotao.web.user;
…
public class UserRegisterController extends SimpleFormController {
…
@Override
protected void onBindAndValidate(HttpServletRequest request,
Object command, BindException errors) throws Exception {
User user = (User) command;
if (bbtForum.isExsitUserName(user.getUserName())) {①通過業(yè)務(wù)對象完成檢驗(yàn)
errors.rejectValue("userName", "exists.userName", "用戶名已經(jīng)存在");
}
}
}
我們在UserRegisterController覆蓋了父類的onBindAndValidate()方法,通過BbtForum業(yè)務(wù)對象的方法判斷userName是否已經(jīng)被占用,如果已經(jīng)被占用,將相應(yīng)錯(cuò)誤添加到errors對象中。
通過錯(cuò)誤標(biāo)簽顯示錯(cuò)誤
當(dāng)存在合法性檢查錯(cuò)誤時(shí),請求被導(dǎo)向到formView的表單頁面中。但是如果register.jsp表單頁面沒有做任何配合操作,校驗(yàn)錯(cuò)誤的信息就象空氣和電磁波一樣,雖然存在但卻看不到,如果我們在register.jsp中相應(yīng)地添加一些Spring錯(cuò)誤標(biāo)簽這面魔法鏡,錯(cuò)誤信息就現(xiàn)形了。下面我們對register.jsp視圖文件進(jìn)行調(diào)整,加入顯示校驗(yàn)錯(cuò)誤的標(biāo)簽:
代碼清單 6 register.jsp:添加錯(cuò)誤標(biāo)簽
…
<form:form>
用戶名:<form:input path="userName" />
<font color="red"><form:errors path="userName" /></font>①userName的校驗(yàn)錯(cuò)誤
<br>
密 碼:<form:password path="password" />
<font color="red"><form:errors path="password" /></font>②password的校驗(yàn)錯(cuò)誤
<br>
Email:<form:input path="email" />
<font color="red"><form:errors path="email" /></font> ③email的校驗(yàn)錯(cuò)誤
<br>
<input type="submit" value="注冊" />
<input type="reset" value="重置" />
</form:form>
…
由于我們在構(gòu)造錯(cuò)誤時(shí),使用了錯(cuò)誤代碼,錯(cuò)誤代碼是引用國際化資源的憑借。為了讓錯(cuò)誤代碼生效,我們就必須提供相應(yīng)的國際化資源。假設(shè)我們將錯(cuò)誤資源放在基名為errors的國際化資源文件中,提供諸如errors.properties和errors_zh_CN.properties的國際化資源文件,那么錯(cuò)誤信息就可以做到國際化了。以下是errors.properties資源文件的內(nèi)容(綠色部分為錯(cuò)誤代碼):
required.username=user name can't be empty.
required.password=password can't be empty.
required.email=email can't be empty.
invalid.email=email is valid.
exists.userName=user name already existed.
將諸如errors.properties和errors_zh_CN.properties的整套資源文件都放到類路徑下后,還需要在上下文中引用這些國際化資源。因?yàn)閲H化資源信息僅需要在Web展現(xiàn)層使用,所以直接在DispatcherServlet上下文對應(yīng)的baobaotao-servlet.xml配置文件中聲明就可以了:
代碼清單 7 baobaotao-servlet.xml
<bean id="messageSource" ① 注意一定要使用“messageSource”這個(gè)Bean名稱
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>errors</value> ② 指定資源文件基名稱
</list>
</property>
</bean>
…
通過以上的配置后,故意填寫一個(gè)錯(cuò)誤的注冊信息,在提交表單后你將看到如下形如以下的錯(cuò)誤提示頁面:
小結(jié)
雖然Spring MVC允許你使用不同類型的處理器,但絕大多數(shù)情況下我們使用控制器(Controller)處理請求。Spring MVC為不同需求提供了多種類型的控制器,控制器一般擁有一個(gè)特定用途的工作流程,如表單控制器編制了表單處理通用工作流程,你僅需要實(shí)現(xiàn)SimpleFormController特定方法,并配置使用Spring表單標(biāo)簽就可以輕松完成表單功能的開發(fā)了。
總結(jié)
以上是生活随笔為你收集整理的Spring MVC的表单控制器——SimpleFormController .的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简明 XHTML 1.0 参考手册
- 下一篇: Apache Tomcat Config