Spring MVC會根據(jù)請求方法簽名不同,將請求消息中信息以一定方式轉(zhuǎn)換并綁定到請求方法的參數(shù)中。
1.數(shù)據(jù)綁定流程
Spring MVC通過反射機制對目標(biāo)處理方法的簽名進行分析,并將請求消息綁定到處理方法的參數(shù)上。數(shù)據(jù)綁定的核心部件是Databinder

1.Spring MVC框架將ServletRequest對象及處理方法的參數(shù)實例傳遞給DataBinder。
2.DataBinder調(diào)用裝配在Spring Web上下文中的ConversionService組件進行數(shù)據(jù)類型轉(zhuǎn)換、
數(shù)據(jù)格式化工作,并將ServletRequest中的消息填充到參數(shù)對象中。3.然后再調(diào)用Validator組件對已經(jīng)綁定的請求消息數(shù)據(jù)的參數(shù)對象進行數(shù)據(jù)合法性校驗。
4.最終生成數(shù)據(jù)綁定結(jié)果BindingResult對象,BindingResult包含已完成數(shù)據(jù)綁定的參數(shù)對象,還包含相應(yīng)的校驗錯誤的對象。
5.Spring MVC抽取BindingResult中的參數(shù)對象及校驗對象,將它們賦給處理方法的相應(yīng)參數(shù)。
2.數(shù)據(jù)轉(zhuǎn)換(ConversionService)
在Java語言中,在java.beans包中提供了一個PropertyEditor接口來進行數(shù)據(jù)轉(zhuǎn)換(只能用于字符串和Java對象的轉(zhuǎn)換)。其功能就是將一個字符串轉(zhuǎn)換為一個Java對象。
Spring 3.0,添加了一個通用的類型轉(zhuǎn)換模塊,位于org.springframework.core.convert包中。
2.1 ConversionServiceboolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType)
org.springframework.core.convert.ConversionService是Spring類型轉(zhuǎn)換的核心接口。
- boolean canConvert(Class<?> sourceType, Class<?> targetType)
判斷是否可以將一個Java類轉(zhuǎn)換為另一個Java類
- boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType)
TypeDescriptor不但描述了需要轉(zhuǎn)換類的信息,還描述了類的上下文信息。這樣可以利用這些信息做出更多的各種靈活的控制。
- <T> T convert(Object source, Class<T> targetType)
將源類型對象轉(zhuǎn)換為目標(biāo)類型對象
- Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)
將源類型從源類型對象轉(zhuǎn)換為目標(biāo)類型對象,通常利用到類中的上下文信息
可以利用org.springframework.context.support.ConversionServiceFactoryBean在Spring的上下文中定義一個ConversionService。Spring將自動識別出上下文種的ConversionService,并在Spring MVC處理方法的參數(shù)綁定中使用它進行數(shù)據(jù)轉(zhuǎn)換。
<bean class="org.springframework.context.support.ConversionServiceFactoryBean"/>
2.2 Spring支持的轉(zhuǎn)換器
Spring 在org.springframework.core.convert.converter包中定義了3種類型的轉(zhuǎn)換器接口,我們可以實現(xiàn)其中任意一種轉(zhuǎn)換接口,并將它作為自定義轉(zhuǎn)換器注冊到ConversionServiceFactoryBean當(dāng)中。
- Converter<S, T>
Spring中最簡單的一個轉(zhuǎn)換器接口。該方法負責(zé)將S類型轉(zhuǎn)換為T類型的對象。
- ConverterFactory<S, R>
如果希望將一種類型的對象轉(zhuǎn)換為另一種類型及其子類對象,比如將String類型轉(zhuǎn)換為Number以及Number的子類Integer、Double等對象。就需要一系列的Converter。該接口的作用就是將這一系列的相同的Converter封裝在一起。
- GenericConverter
Converter<S,T>只是負責(zé)將一個類型的對象轉(zhuǎn)換為另一個類型的對象,它并沒有考慮類型對象的上下文信息。因此不能完成復(fù)雜類型的轉(zhuǎn)換工作。而該接口會根據(jù)源類型的對象及其上下文進行類型轉(zhuǎn)換。
Code
public class User implements Serializable {
private String loginname;
private Date birthday;
public String getLoginname() {
return loginname;
}
public void setLoginname(String loginname) {
this.loginname = loginname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Sign Up</title>
</head>
<body>
<form action="/user/register" method="post">
<table>
<tr>
<td><label>登錄名:</label></td>
<td><input type="text" id="loginname" name="loginname"></td>
</tr>
<tr>
<td><label>生日:</label></td>
<td><input type="text" id="birthday" name="birthday"></td>
</tr>
<tr>
<td>
<input type="submit" id="submit" value="登錄">
</td>
</tr>
</table>
</form>
</body>
</html>
@RequestMapping(value = "register", method = RequestMethod.POST)
public String register(@ModelAttribute User user, Model model) {
model.addAttribute("user", user);
return "success";
}
這時候,前臺輸入的生日為String格式的。而User實體定義的是Date時間類型的。那么后臺再接收的時候,就會報錯。
這時候,我們就自定義類型轉(zhuǎn)換器。實現(xiàn)ConversionService里面的最簡單的Converter<S,T>
public class StringToDateConverter implements Converter<String, Date> {
/**
* 日期類型模板,如yyyy-MM-dd
*/
private String datePattern;
public void setDatePattern(String datePattern) {
this.datePattern = datePattern;
}
@Override
public Date convert(String date) {
Date result = null;
try {
SimpleDateFormat dateFormat = new SimpleDateFormat(this.datePattern);
result = dateFormat.parse(date);
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}
}
- 定義xml節(jié)點
<!--裝配自定義的類型轉(zhuǎn)換器-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--自定義Date類型轉(zhuǎn)換器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<bean class="utils.StringToDateConverter" p:datePattern="yyyy-MM-dd"/>
</property>
</bean>
注冊方法
1.InitBinder(不推薦使用java.beans.PropertyEditor)
剛才上面的注冊方式是通過xml配置進行的操作,那么我們可以不借助xml配置,使用@InitBinder添加自定義編輯轉(zhuǎn)換數(shù)據(jù)。這里就用到了Java自身的PropertyEditor類。
- 自定義方法實現(xiàn)PropertyEditor的相關(guān)類
public class DateEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
Date date = dateFormat.parse(text);
setValue(date);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
- 在控制器處進行注冊初始化
@InitBinder
public void initBinder(WebDataBinder binder){
binder.registerCustomEditor(Date.class,new DateEditor());
}
2.WebBindingInitializer(不推薦使用java.beans.PropertyEditor)
如果這個數(shù)據(jù)轉(zhuǎn)換需要在系統(tǒng)多處使用,那么這個自定義轉(zhuǎn)換器方法需要進行全局注冊。使用WebBindingInitializer進行全局范圍的注冊。
3.數(shù)據(jù)格式化(Fommatter<T>)
Spring使用Converter轉(zhuǎn)換器進行源類型對象到目標(biāo)類型對象的轉(zhuǎn)換。但是Converter并不能夠進行輸入或輸出的信息的格式化。
Spring 3.0引入格式化轉(zhuǎn)換框架,org.springframework.format,F(xiàn)ormatte<T>為最重要的接口。
Converter接口是完成任意Object與Object之間的轉(zhuǎn)換,而Formatter是完成任意Object與String的轉(zhuǎn)換。所以Formatter接口更適合在Web層,處理用戶表單提交的數(shù)據(jù)格式化。
Formatter<T>接口,完成T類型對象的格式化和解析功能。
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
3.1重要接口
- Printer<T>
格式化顯示接口
- Parser<T>
T parse(String text, Locale locale) throws ParseException;
解析接口,參考本地信息,將一個格式化后的字符串轉(zhuǎn)換為T類型的對象。
- Formatter<T>
Formatter<T>繼承上面兩個接口類,具備所繼承接口的所有功能。
- FormatterRegistrar
注冊格式化轉(zhuǎn)換器。一般很少單獨用到,我們一般用到的FormattingConversionServiceFactoryBean這個里面已經(jīng)封裝了這個接口對象。
- AnnotationFormatterFactory<A extends Annotation>
注解驅(qū)動的字段格式化工廠,用于創(chuàng)建帶注解的對象字段的Printer和Parser。即是用于格式化和解析帶注釋的對象字段。
//注解A的應(yīng)用范圍,哪些屬性類可以標(biāo)注A注解
Set<Class<?>> getFieldTypes();
//根據(jù)A注解,獲取特定屬性類型Printer
Printer<?> getPrinter(A annotation, Class<?> fieldType);
//根據(jù)A注解,獲取特定屬性類型Parser
Parser<?> getParser(A annotation, Class<?> fieldType);
3.2 自定義實現(xiàn)Formatter接口(了解下實現(xiàn)方式)
- 后臺代碼
public class DateFormatter implements Formatter<Date> {
private String datePattern;
private SimpleDateFormat dateFormat;
public DateFormatter(String datePattern) {
this.datePattern = datePattern;
dateFormat = new SimpleDateFormat(datePattern);
}
@Override
public Date parse(String text, Locale locale) throws ParseException {
Date result = null;
try {
result = dateFormat.parse(text);
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}
@Override
public String print(Date object, Locale locale) {
return dateFormat.format(object);
}
}
- xml配置
<!--裝配自定義格式轉(zhuǎn)化器-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--配置自定義格式化轉(zhuǎn)換器bean-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<bean class="utils.DateFormatter" c:datePattern="yyyy-MM-dd"/>
</property>
</bean>
3.3 使用系統(tǒng)內(nèi)置的轉(zhuǎn)換器(推薦這種方式)
當(dāng)然Spring本身就提供了很多內(nèi)置的轉(zhuǎn)換器,不需要我們再寫多余的代碼。比如上面我們自定義的時間格式轉(zhuǎn)化器。Spring內(nèi)置的org.springframework.format.datetime包中就有對應(yīng)的DateFormatter實現(xiàn)類。
我們只需要定義xml就可以了,如下。
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<bean class="org.springframework.format.datetime.DateFormatter" c:pattern="yyyy-MM-dd"/>
</property>
</bean>
3.4 自定義使用FormatterRegister注冊Formatter(只需要了解,無須掌握,太麻煩)
前面我們直接在xml里面注冊Formatter實現(xiàn)類,那么我們還可以直接在xml里面注冊Registrar,來替代直接注冊Formatter。
- 后臺代碼
自定義出FormatterRegistrar類
public class CustomerFormatterRegistrar implements FormatterRegistrar {
private DateFormatter dateFormatter;
public CustomerFormatterRegistrar(DateFormatter dateFormatter) {
this.dateFormatter = dateFormatter;
}
@Override
public void registerFormatters(FormatterRegistry registry) {
registry.addFormatter(dateFormatter);
}
}
自定義出DateFormmater類
public class DateFormatter implements Formatter<Date> {
private String datePattern;
private SimpleDateFormat dateFormat;
public DateFormatter(String datePattern) {
this.datePattern = datePattern;
dateFormat = new SimpleDateFormat(datePattern);
}
@Override
public Date parse(String text, Locale locale) throws ParseException {
Date result = null;
try {
result = dateFormat.parse(text);
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}
@Override
public String print(Date object, Locale locale) {
return dateFormat.format(object);
}
}
- xml配置
<!--裝配自定義格式轉(zhuǎn)化器-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--在Spring上下文定義出自定義的時間轉(zhuǎn)化器組件-->
<bean id="dateFormatter" class="utils.DateFormatter" c:datePattern="yyyy-MM-dd"></bean>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatterRegistrars">
<bean class="utils.CustomerFormatterRegistrar" c:dateFormatter-ref="dateFormatter"></bean>
</property>
</bean>
3.5 使用注解的方式來進行格式化工作(AnnotationFormatterFactory<A extends Annotation>)
前面的例子無論是自定義實現(xiàn)數(shù)據(jù)格式工作還是使用系統(tǒng)內(nèi)置的類,都需要通過進行繁瑣的xml配置。現(xiàn)在我們直接使用注解_Annotation的方式進行實現(xiàn)格式化工作。
org.springframework.format.annotation 定義了兩個格式化的注解類型
- 1.DateTimeFormat
@DateTimeFormat 注解可以對java.util.Date、java.util.Calendar等時間類型的屬性進行標(biāo)注。該類支持下面三種互斥屬性
==互斥屬性指的是只能擁有其一,不然同時具備。==
//自定義時間格式
private String pattern;
private String stylePattern;
private ISO iso;
stylePattern
/**
* Set the two character to use to format date values. The first character used for
* the date style, the second is for the time style. Supported characters are
* <ul>
* <li>'S' = Small</li>短日期/時間的樣式
* <li>'M' = Medium</li>中日期/時間的樣式
* <li>'L' = Long</li>長日期/時間的樣式
* <li>'F' = Full</li>完整日期/時間的樣式
* <li>'-' = Omitted</li>忽略日期/時間的樣式
* <ul>
* This method mimics the styles supported by Joda-Time.
* @param stylePattern two characters from the set {"S", "M", "L", "F", "-"}
* @since 3.2
*/
public void setStylePattern(String stylePattern) {
this.stylePattern = stylePattern;
}
IOS幾種可選值
formats.put(ISO.DATE, "yyyy-MM-dd");
formats.put(ISO.TIME, "HH:mm:ss.SSSZ");
formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
- 2.NumberFormat
NumberFormat可對類似數(shù)字類型的屬性進行標(biāo)注,它擁有兩個互斥的屬性。
String pattern()
Style style()
style可選枚舉值
enum Style {
/**
* The default format for the annotated type: typically 'number' but possibly
* 'currency' for a money type (e.g. {@code javax.money.MonetaryAmount)}.
* @since 4.2
*/
DEFAULT,
/**
* The general-purpose number format for the current locale.
*/
NUMBER,
/**
* The percent format for the current locale.
*/
PERCENT,
/**
* The currency format for the current locale.
*/
CURRENCY
}
代碼演示
- 后臺代碼新建需要進行數(shù)據(jù)轉(zhuǎn)換及格式化的類。省略了get、set方法
public class User implements Serializable {
private String loginname;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
/**
* 薪水,以財務(wù)格式接收
*/
@NumberFormat(style = NumberFormat.Style.NUMBER, pattern = "#,###")
private double salary;
/**
* 業(yè)績完成比例
*/
@NumberFormat(style = NumberFormat.Style.PERCENT)
private double performance;
/**
* 薪水的貨幣類型展示
*/
@NumberFormat(style = NumberFormat.Style.CURRENCY)
private double salaryDisplay;
}
- xml配置
<mvc:annotation-driven/>