Spring MVC中基於自定義Editor的表單數據處理技巧

本文出處:http://blog.csdn.net/chaijunkun/article/details/8642642,轉載請註明。由於本人不定期會整理相關博文,會對相應內容作出完善。因此強烈建議在原始出處查看此文。


面向對象的編程方式極大地方便了程序員在管理數據上所花費的精力。在基於Spring MVC的Web開發過程當中,可以通過對象映射的方式來管理表單提交上來的數據,而不用去一個一個地從request中提取出來。另外,這一功能還支持基本數據類型的映射。例如in、long、float等等。這樣我們就能從傳統單一的String類型中解脫出來。然而,應用是靈活的。我們對數據的需求是千變萬化的。有些時候我們需要對錶單的數據進行兼容處理。


例如日期格式的兼容:

中國的日期標註習慣採用yyyy-MM-dd格式,歐美習慣採用MM/dd/yyyy。雖然兩種格式都是日期的標註方法,但是往往我們要想達到兼容的目的必須做繁瑣的轉換。


例如價格的兼容:

價格無非就是一串數字,我們經常用的就是0.00這種表達形式,而對於金額較大的價格我們還習慣採用0,000.00這樣帶有逗號分隔的價格表述形式。


其實Spring MVC中已經考慮到了這個問題,在Controller中可以在初始化綁定的時候註冊一個編輯器。當表單提交過來的數據映射到某一特定類型(甚至是特定參數)時可以按照自定義的方法進行轉換。(除二進制方式傳輸過來的數據以外,通常我們認爲所有傳過來的參數不論是什麼內容,一律認爲是字符串)


下面我虛構了一個需求:

我有一個表單,裏面需要填寫用戶名、生日和積分。這分別代表了String類型、Date類型和Long類型。下面是表單內容:

<form action="getObj.do" method="post">
	<table>
		<tr>
			<td>用戶名:</td>
			<td><input type="text" name="userName" value="Name Test" /></td>
			<td>*普通字符串</td>
		</tr>
		<tr>
			<td>生日:</td>
			<td><input type="text" name="birthday" value="2013-3-7" /></td>
			<td>*支持格式: yyyy-MM-dd 或 MM/dd/yyyy</td>
		</tr>
		<tr>
			<td>積分:</td>
			<td><input type="text" name="score" value="1,000" /></td>
			<td>*支持純數字或帶逗號分隔的數字</td>
		</tr>
		<tr>
			<td colspan="3"><input type="submit" value="提交" /></td>
		</tr>
	</table>
</form>


這裏根據表單,我們映射了如下的一個表單對象,這裏對象的屬性名稱要和上面表單的字段name一致:

package blog.csdn.net.chaijunkun.formObjs;

import java.util.Date;

public class UserInfo {
	
	private String userName;
	
	private Date birthday;
	
	private Long score;

	//getters and setters...

}

那麼我們想接收這樣一個表單數據,可以寫一個對錶單處理的方法:

package blog.csdn.net.chaijunkun.controller;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import blog.csdn.net.chaijunkun.formObjs.UserInfo;

@Controller
public class ObjController {
	
	private static Logger logger= Logger.getLogger(ObjController.class);
	
	public ObjController(){
		logger.info("對象映射控制器初始化");
	}
	
	@RequestMapping(value="/getObj.do")
	public String modifyUser(HttpServletRequest request,
			HttpServletResponse response,Map<String, Object> model,
			UserInfo userInfo){
		logger.info("收集對象信息");
		model.put("userInfo", userInfo);		
		return "user";
	}

}

如果僅僅是這麼寫,當然還不能做到多格式兼容。我們需要寫一個針對日期和Long型的格式兼容編輯器。編輯器需要至少繼承自類:java.beans.PropertyEditorSupport。當然,也可以繼承Spring內置的一些編輯器,例如:org.springframework.beans.propertyeditors.CustomNumberEditor,這個是專門用來處理數字轉換的。無論是繼承哪一個,方法都是一樣的:


第一步:重寫公有的void setAsText(String text)方法;

第二步:將轉換好的數據調用setValue(Object obj)進行寫入。


下面我們先實現一個日期兼容的編輯器:

package blog.csdn.net.chaijunkun.editors;

import java.beans.PropertyEditorSupport;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.List;

import org.springframework.util.StringUtils;

/**
 * 多種日期處理器
 * @author chaijunkun
 * @since 2013年03月08日
 */
public class MultiDateParseEditor extends PropertyEditorSupport {
	
	private List<DateFormat> dateFormats;

	private boolean allowEmpty;

	private final int exactDateLength;

	public MultiDateParseEditor(List<DateFormat> dateFormats, boolean allowEmpty){
		if (dateFormats== null || dateFormats.size() == 0){
			throw new IllegalArgumentException("Param dateFormats could not be empty");
		}
		this.dateFormats = dateFormats;
		this.allowEmpty = allowEmpty;
		this.exactDateLength = -1;
	}

	public MultiDateParseEditor(List<DateFormat> dateFormats, boolean allowEmpty, int exactDateLength){
		if (dateFormats== null || dateFormats.size() == 0){
			throw new IllegalArgumentException("Param dateFormats could not be empty");
		}
		this.dateFormats = dateFormats;
		this.allowEmpty = allowEmpty;
		this.exactDateLength = exactDateLength;
	}

	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		if (this.allowEmpty && !StringUtils.hasText(text)) {
			// Treat empty String as null value.
			setValue(null);
		}else if (text != null && this.exactDateLength >= 0 && text.length() != this.exactDateLength) {
			throw new IllegalArgumentException(
					"Could not parse date: it is not exactly" + this.exactDateLength + "characters long");
		}else {
			ParseException lastException = null;
			for (DateFormat dateFormat : dateFormats) {
				try {
					setValue(dateFormat.parse(text));
					return;
				} catch (ParseException e) {
					lastException = e;
				}
			}
			throw new IllegalArgumentException("Could not parse date: " + lastException.getMessage(), lastException);
		}
	}
}


然後我們再來寫一個針對Long型的編輯器,可以支持帶逗號分隔和不帶逗號分隔的數值表達形式:

package blog.csdn.net.chaijunkun.editors;

import org.springframework.beans.propertyeditors.CustomNumberEditor;

public class MyLongEditor extends CustomNumberEditor  {

	public MyLongEditor(){
		super(Long.class, true);
	}

	@Override
	public void setAsText(String text){
		if ((text== null) || text.trim().equals("")){
			setValue(null);
		}else{
			Long value= null;
			try{
				//按照標準的數字格式嘗試轉換
				value= Long.parseLong(text);
			}catch(NumberFormatException e){
				//嘗試去除逗號 然後再轉換
				text= text.replace(",", "");
				value= Long.parseLong(text);
			}
			//轉好之後將值返給被映射的屬性
			setValue(value);			
		}
	}

}

好了,這兩個編輯器寫好了,如何讓它們發揮作用呢?這需要在Controller內加一個數據轉換時的綁定方法:

@InitBinder
public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder){
	List<DateFormat> dateFormats = new LinkedList<DateFormat>();
	dateFormats.add(new SimpleDateFormat("yyyy-MM-dd"));
	dateFormats.add(new SimpleDateFormat("MM/dd/yyyy"));
	binder.registerCustomEditor(Date.class, new MultiDateParseEditor(dateFormats, true));
	binder.registerCustomEditor(Long.class, new MyLongEditor());
}

上面的代碼作用就是:當接收到表單數據,Spring發現參數名能夠與對象屬性相對應,而轉換的類型恰好也是在上述代碼中註冊過的類似,則會將數據內容按照指定的編輯器來做轉換。

我們來試一下:

如下圖所示:


這裏日期是按照yyyy-mm-dd格式填寫的,Long型是按照帶逗號分隔填寫的。我們提交一下試試:


日期和Long類型數據都能夠正常識別。我們再來看看另外兩種格式:


這裏日期採用了MM/dd/yyyy的格式,Long類型使用的普通的表示方法。我們提交一下看看:


同樣,數據被正確識別了。


通過以上方法,我們成功地兼容了多種數據格式。


寫在後面:

其實針對日期格式,我開始的時候想寫成下面代碼那樣來實現兼容:

@InitBinder
public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder){
	binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
	binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("MM/dd/yyyy"), true));
}
後來我發現,這樣寫之後只支持MM/dd/yyyy格式的日期,提交yyyy-MM-dd格式的日期後會拋出異常。看來,對於同一類型,在一個控制器裏只能註冊一個編輯器,而且是最後一個被註冊的才起作用。


另外,在文章剛開始的時候寫到,不僅可以按類型,甚至是某一類型的某個屬性都可以按照自己的要求定製編輯器,同時不影響其它同類型的屬性。這個很容易,在registerCustomEditor方法中還有一個重載的方法,第二個參數可以指定具體的屬性名稱。這樣就很容易控制細粒度了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章