程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Spring MVC的表單控制器

Spring MVC的表單控制器

編輯:關於JAVA

概述

大多數Web應用都會遇到需要填寫表單的頁面,當表單提交成功後,表單的數據被傳送給Web服務器中處理。處理成功後導向到一個成功頁面,如果操作失敗則導向到一個錯誤報告頁面。此外,在表單數據處理之前還會進行表單數據的驗證,保證客戶端提交的表單數據是合法有效的,如果數據不合法,請求返回到原表單頁面中,以便用戶根據錯誤信息進行修改。

假設你想成為論壇的用戶時,必須填寫一張用戶注冊表單,這可能包括用戶名、密碼、Email等注冊信息。用戶提交表單後,服務器驗證注冊數據合法性,如果你填寫的信息是合法的,系統將在數據庫中創建一個新用戶,用戶注冊就完成了。

用戶注冊表單控制器

通過擴展SimpleFormController可以按照標准的表單處理流程處理用戶注冊的請求,UserRegisterController用於負責處理用戶注冊的請求:

代碼清單 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); ①指定命令對象(這時也稱表單對象)的類型
}
public void setBbtForum(BbtForum bbtForum) {
this.bbtForum = bbtForum;
}
②通過該方法處理表單提交請求
protected void doSubmitAction(Object command) throws Exception {
User user = (User) command;
bbtForum.registerUser(user);
}
}
  在①處指定表單對象的類型,以便控制器自動將表單數據綁定到表單對象中,你也可以直接在配置文件中通過commandClass屬性進行設置:

<property name="commandClass" value=" com.baobaotao.domain.User"/>

在②處復寫了doSubmitAction()方法,在該方法內部通過調用業務層的bbtForum保存表單對象,創建新用戶。當你不需要返回模型對象給成功頁面時,復寫doSubmitAction()方法是最佳的選擇,因為該方法沒有返回值。如果需要返回模型對象給成功頁面,那麼就必須復寫表單控制器的onSubmit ()方法。用戶注冊成功後,我們一般需要在成功頁面中根據用戶信息提供個性化的內容,這就要求控制器返回相應的User模型對象,此時需要在UserRegisterController中復寫onSubmit ()方法:


protected ModelAndView onSubmit (Object command, BindException errors)
throws Exception {
User user = (User) command;
bbtForum.registerUser(user);
return new ModelAndView(getSuccessView(), "user", user);①user中包含注冊用戶的信息
}

當你復寫onSubmit ()方法後,doSubmitAction()方法就不會得到執行了, onSubmit ()方法比doSubmitAction()方法具有更高的調用優先級,所以你只要根據要求復寫兩者中的一個方法就可以了。在onSubmit ()中返回的ModelAndView的邏輯視圖名應該是通過表單控制器的successView屬性指定而不應該硬編碼,所以在①處我們通過getSuccessView()獲取這個配置值。

表單控制器的工作流程從表單頁面提交開始,處理成功後轉向成功頁面,這個流程涉及到兩個視圖:表單頁面和成功頁面,這需要在表單控制器中通過屬性進行定義:

<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屬性指定表單錄入頁面對應的邏輯視圖名,而successView屬性表示成功頁面的視圖邏輯名。通過代碼清單 2前後綴視圖解析器的處理,它們將分別對應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表單標簽綁定表單對象,以便根據表單對象初始值生成表單頁面,在校驗失敗後能夠重現提交前的表單數據。讓我們看看這個register.jsp用戶注冊頁面的內容:

代碼清單 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表單標簽,可以直接和/registerUser.html控制器
綁定(fromView),無需通過action指定提交的目標地址。
<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>

在①處,應用Spring的表單標簽定義了一個能夠和表單對象綁定的頁面表單。和Struts不同的是作為表單標簽的<form:form>元素無需設定提交地址(在Struts中必須指定表單標簽的action屬性),Spring MVC能夠自動根據控制器的formView屬性獲知該表單頁面的提交地址。使用過Struts Action開發表單提交功能的讀者也許會知道開發Struts處理表單功能是比較麻煩,因為可能會為了開發一個表單設計多個Action:一個用戶初始化表單,另一個用於提交表單。而Spring的SimpleFormController的高明之處在於,它已經將表單處理工作流程編制到控制器中,我們僅需要在子類中復寫開放出方法就可以充分享受預定義工作流程的好處。我們應該如何有選擇地覆蓋父類方法,以便正確地影響表單工作流程呢?這回答這個問題需要對SimpleFormController的工作流程有一個詳細的了解。

表單控制器完整工作流程

使用SimpleFormController時,你無需為初始化表單編寫額外的控制器,當你通過GET請求訪問表單控制器時,表單控制器自動將請求導向到表單錄入頁面。而當你通過POST請求訪問表單控制器時,表單控制器執行表單提交的業務,根據處理成功與否,或導向到成功頁面,或導向到表單錄入頁面(當發生異常時導向到錯誤頁面)。

SimpleFormController的工作流程比較復雜,我們通過下面的流程圖對此進行描述:

1.當表單控制器接收到GET請求時,它調用formBackingObject()方法,創建表單對象。該方法可以被子類覆蓋,對於編輯操作的表單來說,你可以通過該方法從數據庫中加載表單對象,當表單頁面顯示時,表單顯示出待編輯的數據了;

2.表單對象和頁面表單數據之間需要通過屬性編輯器實現雙向轉化,對於非基本數據類型或String類型的屬性來說,你可能需要注冊一些自定義編輯器。你可以通過覆蓋initBinder()方法,通過調用binder.registerCustomEditor()的方法注冊編輯器;

3.表單對象通過bindOnNewForm屬性(可以通過配置設置,默認為false)判斷是否需要將GET請求參數綁定到formBackingObject()方法創建的表單對象中。如果bindOnNewForm為true,執行綁定操作,在綁定完成後,還將調用onBindOnNewForm()回調方法(子類可以提供具體實現)。否則到下一步。不過一般情況下,GET請求參數是用於加載等編輯表單對象的ID值,如topicId、forumId等,一般無需進行綁定;

4.調用referenceData()方法(子類可提供具體實現)准備一些關聯的數據,如性別下拉框數據,學歷下拉框數據等。一般采用ModelMap創建視圖業務中需要用到的請求屬性數據,鍵為屬性名,值為屬性值,如ModelMap("param1", "paramValue1");

5.使用控制器formView定義的視圖渲染表單對象;

6.用戶填寫或更改表單後,提交表單,向表單控制器發起一個POST請求;

7.接收到POST請求時,表單控制器知道這是一個表單數據提交的操作,所以啟動表單提交處理流程;

8.首先通過sessionForm屬性判斷表單控制器是否啟用了Session。如果啟用了Session,直接從Session中取出原表單對象,否則再次調用formBackingObject()方法構造出一個表單對象。sessionForm默認為false,可以通過配置進行調整,啟用Session可能提高運行性能,但會占用一定的內存;

9.將POST請求參數填充到表單對象中;

10.調用onBind()方法,該方法允許你在表單填充完成後,合法性校驗之前執行一些特定的操作;

11.如果validateOnBinding屬性設置為true,注冊在控制器中的校驗器開始工作,對表單對象的屬性值執行合法性校驗。如果有合法性錯誤,將被注冊到Errors對象中(關於如何注冊校驗器,我們將稍後介紹);

12.調用onBindAndValidate()方法,該方法允許你在數據綁定及合法性校驗後,執行一些額外的自定義操作,你也可以在這裡,執行一些額外的合法性校驗;

13.調用processFormSubmission()方法處理提交任務,該方法內部又包含後續幾步工作;

14.判斷方法入參傳入errors是否包含錯誤,如果包含錯誤返回到formView對應的表單頁面中,否則到下一步;

15.通過isFormChangeRequest()方法(默認為false)判斷請求是否為表單更改請求,如果為true,調用onFormChange()方法,然後返回到formView對應的表單頁面,否則到下一步;

16.如果子類覆蓋了onSubmit()方法,執行之,否則執行子類的doSubmitAction()方法。通過這兩者之一完成業務的處理,然後返回successView屬性指定的成功頁面。

我們可以按照以上表單控制器的工作流程,根據業務需要有選擇地覆蓋一些父類的方法完成特定的操作。假設我們在開發一個編輯用戶信息的功能,在展現表單前需要先從數據庫中查詢出用戶信息並在更改表單中展現,這時,我們僅需覆蓋formBackingObject()方法,執行查詢操作就可以了,其代碼形如下所示:

① 根據請求參數從數據庫中查詢出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新增的工具類,可以方便地按類型獲取請求參數的值,它位於org.springframework.web.bind包中。

表單數據校驗

當UserRegisterController調用BbtForum#registerUser()方法注冊用戶時,確保User對象數據的合法性是非常重要的,你不希望用戶的Email地址是非法的,用戶名不應和已經用戶名相同。

org.springframework.validation.Validator接口為Spring MVC提供了數據合法性校驗功能,該接口有兩個方法,說明如下: boolean supports(Class clazz):判斷校驗器是否支持指定的目標對象,每一個校驗器負責對一個表單類的對象進行檢驗;

void validate(Object target, Errors errors):對target對象進行合法性校驗,通過Errors返回校驗錯誤的結果。

下面,我們編寫一個負責對User對象進行數據合法性校驗的校驗器,請看以下的代碼:

代碼清單 4 UserValidator:校驗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正則表達式
.compile("(?:w[-._w]*w@w[-._w]*w.w{2,3}$)");
public boolean supports(Class clazz) { ②該校驗器支持的目標類
return clazz.equals(User.class);
}
public void validate(Object target, Errors errors) { ③對目標類對象進行校驗,錯誤記錄在errors中
User user = (User) target; ③-1 造型為User對象
③-2 通過Spring提供的校驗工具類進行簡單的規則校驗
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
"required.username", "用戶名必須填寫");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password",
"required.password", "密碼不能為空");
validateEmail(user.getEmail(), errors); ③-3 校驗Email格式
}
private void validateEmail(String email, Errors errors) {④Email合法性校驗
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email",
"required.email", "Email不能為空");
Matcher m = EMAIL_PATTERN.matcher(email); ④-1 通過正則表達式校驗Email格式
if (!m.matches()) {
errors.rejectValue("email", "invalid.email", "Email格式非法");
}
}
}

在②處,我們聲明該校驗器支持的表單對象為User類,如果錯誤地將UserValidator用於其它對象校驗,Spring MVC就會根據supports()方法駁回操作。

對於一般的空值校驗來說,直接使用Spring提供的ValidationUtils校驗工具類是最簡單的辦法(如③-2所示)。ValidationUtils的rejectIfEmptyOrWhitespace()、rejectIfEmpty()以及Errors的reject()、rejectValue()方法都擁有多個用於描述錯誤的入參,通過下圖進行說明:

1)對應字段:表示該錯誤是對應表單對象的哪一個字段,Spring MVC的錯誤標簽可以通過path屬性訪問該字段錯誤消息;

2)錯誤代碼:表示該錯誤對應資源文件中的鍵名,Spring MVC的錯誤標簽可以據此獲取資源文件中的對應消息。如果希望實現錯誤消息的國際化,你就必須通過錯誤代碼指定錯誤消息;

3)默認消息:當資源文件沒有對應的錯誤代碼時,使用默認消息作為錯誤消息。

我們“驚訝地”發現入參列表並沒有包括需要校驗的目標表單對象,那如何對目標表單對象實施校驗呢?原來目標對象已經包含在errors對象中,在校驗方法內部會從errors中取得目標方法並施加校驗。

在④處,我們通過正則表達式對Email格式進行校驗。我們直接使用JDK 1.4 java.util.regex包中提供的正則表達式工具類完成校驗的工作。由於Email模式是固定的,為了提高性能,我們在①處用final static的方式定義了一個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"> ①裝配校驗器
<bean class="com.baobaotao.domain.UserValidator" />
</property>
</bean>

在①處我們通過validator指定了一個對User表單對象進行校驗的校驗器,如果你有多個校驗器類(很少見),可以通過validators屬性進行指定。

我們通過UserValidator可以很好地完成User對象屬性值的格式檢查,可是仔細想想是否還存在遺漏呢?也許你已經指出:userName不能和數據庫中已有用戶名重復!你當然可以在UserValidator中通過注入業務對象完成userName重復性的校驗,但對於這種需要通過業務對象完成的校驗操作,一種更好的方法是通過覆蓋控制器的onBindAndValidate()方法,直接在控制器中提供檢驗。這帶來了一個好處,UserValidator無需和業務對象打交道,而UserRegisterController本身已經擁有了業務對象的引用,所以調用業務對象執行校驗非常方便。下面的代碼展示了UserRegisterController中onBindAndValidate()的內容:

代碼清單 5 UserRegisterController#onBindAndValidate()通過業務對象完成校驗

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())) {①通過業務對象完成檢驗
errors.rejectValue("userName", "exists.userName", "用戶名已經存在");
}
}
}

我們在UserRegisterController覆蓋了父類的onBindAndValidate()方法,通過BbtForum業務對象的方法判斷userName是否已經被占用,如果已經被占用,將相應錯誤添加到errors對象中。

通過錯誤標簽顯示錯誤

當存在合法性檢查錯誤時,請求被導向到formView的表單頁面中。但是如果register.jsp表單頁面沒有做任何配合操作,校驗錯誤的信息就象空氣和電磁波一樣,雖然存在但卻看不到,如果我們在register.jsp中相應地添加一些Spring錯誤標簽這面魔法鏡,錯誤信息就現形了。下面我們對register.jsp視圖文件進行調整,加入顯示校驗錯誤的標簽:

代碼清單 6 register.jsp:添加錯誤標簽


<form:form>
用戶名:<form:input path="userName" />
<font color="red"><form:errors path="userName" /></font>①userName的校驗錯誤
<br>
密 碼:<form:password path="password" />
<font color="red"><form:errors path="password" /></font>②password的校驗錯誤
<br>
Email:<form:input path="email" />
<font color="red"><form:errors path="email" /></font> ③email的校驗錯誤
<br>
<input type="submit" value="注冊" />
<input type="reset" value="重置" />
</form:form>

由於我們在構造錯誤時,使用了錯誤代碼,錯誤代碼是引用國際化資源的憑借。為了讓錯誤代碼生效,我們就必須提供相應的國際化資源。假設我們將錯誤資源放在基名為errors的國際化資源文件中,提供諸如errors.properties和errors_zh_CN.properties的國際化資源文件,那麼錯誤信息就可以做到國際化了。以下是errors.properties資源文件的內容(綠色部分為錯誤代碼):

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的整套資源文件都放到類路徑下後,還需要在上下文中引用這些國際化資源。因為國際化資源信息僅需要在Web展現層使用,所以直接在DispatcherServlet上下文對應的baobaotao-servlet.xml配置文件中聲明就可以了:

代碼清單 7 baobaotao-servlet.xml

<bean id="messageSource" ① 注意一定要使用“messageSource”這個Bean名稱
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>errors</value> ② 指定資源文件基名稱
</list>
</property>
</bean>

通過以上的配置後,故意填寫一個錯誤的注冊信息,在提交表單後你將看到如下形如以下的錯誤提示頁面:

小結

雖然Spring MVC允許你使用不同類型的處理器,但絕大多數情況下我們使用控制器(Controller)處理請求。Spring MVC為不同需求提供了多種類型的控制器,控制器一般擁有一個特定用途的工作流程,如表單控制器編制了表單處理通用工作流程,你僅需要實現SimpleFormController特定方法,並配置使用Spring表單標簽就可以輕松完成表單功能的開發了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved