Spring MVC會(huì)根據(jù)請(qǐng)求方法簽名不同场钉,將請(qǐng)求消息中信息以一定方式轉(zhuǎn)換并綁定到請(qǐng)求方法的參數(shù)中阻桅。
1.數(shù)據(jù)綁定流程
Spring MVC通過反射機(jī)制對(duì)目標(biāo)處理方法的簽名進(jìn)行分析冰单,并將請(qǐng)求消息綁定到處理方法的參數(shù)上。數(shù)據(jù)綁定的核心部件是Databinder
1.Spring MVC框架將ServletRequest對(duì)象及處理方法的參數(shù)實(shí)例傳遞給DataBinder斤程。
2.DataBinder調(diào)用裝配在Spring Web上下文中的ConversionService組件進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換榆浓、
數(shù)據(jù)格式化工作,并將ServletRequest中的消息填充到參數(shù)對(duì)象中弱贼。3.然后再調(diào)用Validator組件對(duì)已經(jīng)綁定的請(qǐng)求消息數(shù)據(jù)的參數(shù)對(duì)象進(jìn)行數(shù)據(jù)合法性校驗(yàn)蒸苇。
4.最終生成數(shù)據(jù)綁定結(jié)果BindingResult對(duì)象,BindingResult包含已完成數(shù)據(jù)綁定的參數(shù)對(duì)象吮旅,還包含相應(yīng)的校驗(yàn)錯(cuò)誤的對(duì)象溪烤。
5.Spring MVC抽取BindingResult中的參數(shù)對(duì)象及校驗(yàn)對(duì)象,將它們賦給處理方法的相應(yīng)參數(shù)庇勃。
2.數(shù)據(jù)轉(zhuǎn)換(ConversionService)
在Java語言中檬嘀,在java.beans包中提供了一個(gè)PropertyEditor接口來進(jìn)行數(shù)據(jù)轉(zhuǎn)換(只能用于字符串和Java對(duì)象的轉(zhuǎn)換)。其功能就是將一個(gè)字符串轉(zhuǎn)換為一個(gè)Java對(duì)象责嚷。
Spring 3.0鸳兽,添加了一個(gè)通用的類型轉(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)
判斷是否可以將一個(gè)Java類轉(zhuǎn)換為另一個(gè)Java類
- boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType)
TypeDescriptor不但描述了需要轉(zhuǎn)換類的信息,還描述了類的上下文信息爆班。這樣可以利用這些信息做出更多的各種靈活的控制衷掷。
- <T> T convert(Object source, Class<T> targetType)
將源類型對(duì)象轉(zhuǎn)換為目標(biāo)類型對(duì)象
- Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)
將源類型從源類型對(duì)象轉(zhuǎn)換為目標(biāo)類型對(duì)象,通常利用到類中的上下文信息
可以利用org.springframework.context.support.ConversionServiceFactoryBean在Spring的上下文中定義一個(gè)ConversionService柿菩。Spring將自動(dòng)識(shí)別出上下文種的ConversionService戚嗅,并在Spring MVC處理方法的參數(shù)綁定中使用它進(jìn)行數(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)換器接口枢舶,我們可以實(shí)現(xiàn)其中任意一種轉(zhuǎn)換接口懦胞,并將它作為自定義轉(zhuǎn)換器注冊(cè)到ConversionServiceFactoryBean當(dāng)中。
- Converter<S, T>
Spring中最簡(jiǎn)單的一個(gè)轉(zhuǎn)換器接口祟辟。該方法負(fù)責(zé)將S類型轉(zhuǎn)換為T類型的對(duì)象医瘫。
- ConverterFactory<S, R>
如果希望將一種類型的對(duì)象轉(zhuǎn)換為另一種類型及其子類對(duì)象,比如將String類型轉(zhuǎn)換為Number以及Number的子類Integer旧困、Double等對(duì)象醇份。就需要一系列的Converter。該接口的作用就是將這一系列的相同的Converter封裝在一起吼具。
- GenericConverter
Converter<S僚纷,T>只是負(fù)責(zé)將一個(gè)類型的對(duì)象轉(zhuǎn)換為另一個(gè)類型的對(duì)象,它并沒有考慮類型對(duì)象的上下文信息拗盒。因此不能完成復(fù)雜類型的轉(zhuǎn)換工作怖竭。而該接口會(huì)根據(jù)源類型的對(duì)象及其上下文進(jìn)行類型轉(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";
}
這時(shí)候陡蝇,前臺(tái)輸入的生日為String格式的痊臭。而User實(shí)體定義的是Date時(shí)間類型的哮肚。那么后臺(tái)再接收的時(shí)候,就會(huì)報(bào)錯(cuò)广匙。
這時(shí)候允趟,我們就自定義類型轉(zhuǎn)換器。實(shí)現(xiàn)ConversionService里面的最簡(jiǎn)單的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é)點(diǎn)
<!--裝配自定義的類型轉(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>
注冊(cè)方法
1.InitBinder(不推薦使用java.beans.PropertyEditor)
剛才上面的注冊(cè)方式是通過xml配置進(jìn)行的操作潮剪,那么我們可以不借助xml配置,使用@InitBinder添加自定義編輯轉(zhuǎn)換數(shù)據(jù)分唾。這里就用到了Java自身的PropertyEditor類抗碰。
- 自定義方法實(shí)現(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();
}
}
}
- 在控制器處進(jìn)行注冊(cè)初始化
@InitBinder
public void initBinder(WebDataBinder binder){
binder.registerCustomEditor(Date.class,new DateEditor());
}
2.WebBindingInitializer(不推薦使用java.beans.PropertyEditor)
如果這個(gè)數(shù)據(jù)轉(zhuǎn)換需要在系統(tǒng)多處使用,那么這個(gè)自定義轉(zhuǎn)換器方法需要進(jìn)行全局注冊(cè)绽乔。使用WebBindingInitializer進(jìn)行全局范圍的注冊(cè)弧蝇。
3.數(shù)據(jù)格式化(Fommatter<T>)
Spring使用Converter轉(zhuǎn)換器進(jìn)行源類型對(duì)象到目標(biāo)類型對(duì)象的轉(zhuǎn)換。但是Converter并不能夠進(jìn)行輸入或輸出的信息的格式化迄汛。
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類型對(duì)象的格式化和解析功能沉填。
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
3.1重要接口
- Printer<T>
格式化顯示接口
- Parser<T>
T parse(String text, Locale locale) throws ParseException;
解析接口疗隶,參考本地信息,將一個(gè)格式化后的字符串轉(zhuǎn)換為T類型的對(duì)象翼闹。
- Formatter<T>
Formatter<T>繼承上面兩個(gè)接口類斑鼻,具備所繼承接口的所有功能。
- FormatterRegistrar
注冊(cè)格式化轉(zhuǎn)換器猎荠。一般很少單獨(dú)用到坚弱,我們一般用到的FormattingConversionServiceFactoryBean這個(gè)里面已經(jīng)封裝了這個(gè)接口對(duì)象。
- AnnotationFormatterFactory<A extends Annotation>
注解驅(qū)動(dòng)的字段格式化工廠关摇,用于創(chuàng)建帶注解的對(duì)象字段的Printer和Parser荒叶。即是用于格式化和解析帶注釋的對(duì)象字段。
//注解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 自定義實(shí)現(xiàn)Formatter接口(了解下實(shí)現(xiàn)方式)
- 后臺(tái)代碼
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)換器,不需要我們?cè)賹懚嘤嗟拇a愁茁。比如上面我們自定義的時(shí)間格式轉(zhuǎn)化器蚕钦。Spring內(nèi)置的org.springframework.format.datetime包中就有對(duì)應(yīng)的DateFormatter實(shí)現(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注冊(cè)Formatter(只需要了解,無須掌握道宅,太麻煩)
前面我們直接在xml里面注冊(cè)Formatter實(shí)現(xiàn)類,那么我們還可以直接在xml里面注冊(cè)Registrar胸蛛,來替代直接注冊(cè)Formatter污茵。
- 后臺(tái)代碼
自定義出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上下文定義出自定義的時(shí)間轉(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 使用注解的方式來進(jìn)行格式化工作(AnnotationFormatterFactory<A extends Annotation>)
前面的例子無論是自定義實(shí)現(xiàn)數(shù)據(jù)格式工作還是使用系統(tǒng)內(nèi)置的類,都需要通過進(jìn)行繁瑣的xml配置≡嵯睿現(xiàn)在我們直接使用注解_Annotation的方式進(jìn)行實(shí)現(xiàn)格式化工作泞当。
org.springframework.format.annotation 定義了兩個(gè)格式化的注解類型
- 1.DateTimeFormat
@DateTimeFormat 注解可以對(duì)java.util.Date、java.util.Calendar等時(shí)間類型的屬性進(jìn)行標(biāo)注民珍。該類支持下面三種互斥屬性
==互斥屬性指的是只能擁有其一襟士,不然同時(shí)具備。==
//自定義時(shí)間格式
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>短日期/時(shí)間的樣式
* <li>'M' = Medium</li>中日期/時(shí)間的樣式
* <li>'L' = Long</li>長(zhǎng)日期/時(shí)間的樣式
* <li>'F' = Full</li>完整日期/時(shí)間的樣式
* <li>'-' = Omitted</li>忽略日期/時(shí)間的樣式
* <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可對(duì)類似數(shù)字類型的屬性進(jìn)行標(biāo)注嚷量,它擁有兩個(gè)互斥的屬性陋桂。
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
}
代碼演示
- 后臺(tái)代碼新建需要進(jìn)行數(shù)據(jù)轉(zhuǎn)換及格式化的類。省略了get蝶溶、set方法
public class User implements Serializable {
private String loginname;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
/**
* 薪水嗜历,以財(cái)務(wù)格式接收
*/
@NumberFormat(style = NumberFormat.Style.NUMBER, pattern = "#,###")
private double salary;
/**
* 業(yè)績(jī)完成比例
*/
@NumberFormat(style = NumberFormat.Style.PERCENT)
private double performance;
/**
* 薪水的貨幣類型展示
*/
@NumberFormat(style = NumberFormat.Style.CURRENCY)
private double salaryDisplay;
}
- xml配置
<mvc:annotation-driven/>