【SpringMVC】資料轉換 & 資料格式化 & 資料校驗
資料轉換 & 資料格式化 & 資料校驗
資料繫結流程
- Spring MVC 主框架將 ServletRequest 物件及目標方法的入參例項傳遞給 WebDataBinderFactory 例項,以建立 DataBinder 例項物件
- DataBinder 呼叫裝配在 Spring MVC 上下文中的 ConversionService 元件進行資料型別轉換、資料格式化工作。將 Servlet 中的請求資訊填充到入參物件中
- 呼叫 Validator 元件對已經綁定了請求訊息的入參物件進行資料合法性校驗,並最終生成資料繫結結果 BindingData 物件
- Spring MVC 抽取 BindingResult 中的入參物件和校驗錯誤物件,將它們賦給處理方法的響應入參
Spring MVC 通過反射機制對目標處理方法進行解析,將請求訊息繫結到處理方法的入參中。資料繫結的核心部件是 DataBinder,執行機制如下:
資料轉換
- Spring MVC 上下文中內建了很多轉換器,可完成大多數 Java 型別的轉換工作。
- ConversionService converters =
- java.lang.Boolean -> java.lang.String :
org.springframework.core.convert.support.ObjectToStringConverter@f874ca - java.lang.Character -> java.lang.Number : CharacterToNumberFactory@f004c9
- java.lang.Character -> java.lang.String : ObjectToStringConverter@68a961
- java.lang.Enum -> java.lang.String : EnumToStringConverter@12f060a
- java.lang.Number -> java.lang.Character : NumberToCharacterConverter@1482ac5
- java.lang.Number -> java.lang.Number : NumberToNumberConverterFactory@126c6f
- java.lang.Number -> java.lang.String : ObjectToStringConverter@14888e8
- java.lang.String -> java.lang.Boolean : StringToBooleanConverter@1ca6626
- java.lang.String -> java.lang.Character : StringToCharacterConverter@1143800
- java.lang.String -> java.lang.Enum : StringToEnumConverterFactory@1bba86e
- java.lang.String -> java.lang.Number : StringToNumberConverterFactory@18d2c12
- java.lang.String -> java.util.Locale : StringToLocaleConverter@3598e1
- java.lang.String -> java.util.Properties : StringToPropertiesConverter@c90828
- java.lang.String -> java.util.UUID : StringToUUIDConverter@a42f23
- java.util.Locale -> java.lang.String : ObjectToStringConverter@c7e20a
- java.util.Properties -> java.lang.String : PropertiesToStringConverter@367a7f
- java.util.UUID -> java.lang.String : ObjectToStringConverter@112b07f
- ……
- java.lang.Boolean -> java.lang.String :
自定義型別轉換器
- ConversionService 是 Spring 型別轉換體系的核心介面。
- 可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定義一個 ConversionService. Spring 將自動識別出 IOC 容器中的 ConversionService,並在 Bean 屬性配置及 Spring MVC 處理方法入參繫結等場合使用它進行資料的轉換
- 可通過 ConversionServiceFactoryBean 的 converters 屬性註冊自定義的型別轉換器
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.nemo.springmvc.UserConverter"></bean>
</list>
</property>
</bean>
Spring 支援的轉換器
Spring 定義了 3 種類型的轉換器介面,實現任意一個轉換器介面都可以作為自定義轉換器註冊到 ConversionServiceFactroyBean 中:
- Converter<S,T>:將 S 型別物件轉為 T 型別物件
- ConverterFactory:將相同系列多個 “同質” Converter 封裝在一起。如果希望將一種型別的物件轉換為另一種型別及其子類的物件(例如將 String 轉換為 Number 及 Number 子類 (Integer、Long、Double 等)物件)可使用該轉換器工廠類
- GenericConverter:會根據源類物件及目標類物件所在的宿主類 中的上下文資訊進行型別轉換
自定義轉換器示例
<mvc:annotation-driven conversion-service=“conversionService”/>
會將自定義的 ConversionService 註冊到 Spring MVC 的上下文中
<mvc:annotation-driven conversion-ervice="conversionService"></mvc:annotation-driven>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.nemo.springmvc.UserConverter"></bean>
</list>
</property>
</bean>
@RequestMapping("/handle24")
public String handle24(@RequestParam("user") User user) {
System.out.println(user);
return "success";
}
關於 mvc:annotation-driven
<mvc:annotation-driven/>
會自動註冊RequestMappingHandlerMapping、RequestMappingHandlerAdapter 與 ExceptionHandlerExceptionResolver 三個bean。- 還將提供以下支援:
- 支援使用 ConversionService 例項對錶單引數進行型別轉換
- 支援使用 @NumberFormat annotation、@DateTimeFormat 註解完成資料型別的格式化
- 支援使用 @Valid 註解對 JavaBean 例項進行 JSR 303 驗證
- 支援使用 @RequestBody 和 @ResponseBody 註解
既沒有配置 <mvc:default-servlet-handler/>
也沒有配置 <mvc:annotation-driven/>
配置了 <mvc:default-servlet-handler/>
但沒有配置 <mvc:annotation-driven/>
既配置了 <mvc:default-servlet-handler/>
又配置 <mvc:annotation-driven/>
@InitBinder
- 由 @InitBinder 標識的方法,可以對 WebDataBinder 物件進行初始化。WebDataBinder 是 DataBinder 的子類,用於完成由表單欄位到 JavaBean 屬性的繫結
- @InitBinder方法不能有返回值,它必須宣告為void。
- @InitBinder方法的引數通常是是 WebDataBinder
/**
* 不自動繫結物件中的 roleSet 屬性,另行處理。
*/
@InitBinder
public void intBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowFields("roleSet");
}
資料格式化
- 對屬性物件的輸入/輸出進行格式化,從其本質上講依然屬於 “型別轉換” 的範疇。
- Spring 在格式化模組中定義了一個實現 ConversionService 介面的 FormattingConversionService 實現類,該實現類擴充套件了 GenericConversionService,因此它既具有型別轉換的功能,又具有格式化的功能
- FormattingConversionService 擁有一個 FormattingConversionServiceFactroyBean 工廠類,後者用於在 Spring 上下文中構造前者
- FormattingConversionServiceFactroyBean 內部已經註冊了:
- NumberFormatAnnotationFormatterFactroy:支援對數字型別的屬性使用 @NumberFormat 註解
- JodaDateTimeFormatAnnotationFormatterFactroy:支援對日期型別的屬性使用 @DateTimeFormat 註解
- 裝配了 FormattingConversionServiceFactroyBean 後,就可以在 Spring MVC 入參繫結及模型資料輸出時使用註解驅動了。
<mvc:annotation-driven/>
預設建立的 ConversionService 例項即為 FormattingConversionServiceFactroyBean
日期格式化
@DateTimeFormat 註解可對 java.util.Date、java.util.Calendar、java.long.Long 時間型別進行標註:
- pattern 屬性:型別為字串。指定解析/格式化欄位資料的模式,如:”yyyy-MM-dd hh:mm:ss”
- iso 屬性:型別為 DateTimeFormat.ISO。指定解析/格式化欄位資料的ISO模式,包括四種:ISO.NONE(不使用) -- 默 認、ISO.DATE(yyyy-MM-dd) 、ISO.TIME(hh:mm:ss.SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)
- style 屬性:字串型別。通過樣式指定日期時間的格式,由兩位字元組成,第一位表示日期的格式,第二位表示時間的格式:S:短日期/時間格式、M:中日期/時間格式、L:長日期/時間格式、F:完整日期/時間格式、-:忽略日期或時間格式
數值格式化
@NumberFormat 可對類似數字型別的屬性進行標註,它擁有兩個互斥的屬性:
- style:型別為 NumberFormat.Style。用於指定樣式型別,包括三種:Style.NUMBER(正常數字型別)、Style.CURRENCY(貨幣型別)、 Style.PERCENT(百分數型別)
- pattern:型別為 String,自定義樣式,如patter="#,###";
格式化示例
<mvc:annotation-driven></mvc:annotation-driven>
public class User {
@DateTimeFormat(pattern="yyyy/MM/dd")
private Date birthday;
@NumberFormat(pattern="#,###.###")
private Double wages;
}
@RequestMapping("/handle19")
public String handle19(@ModelAttribute("user") User user) {
user.setId(1000);
System.out.println(user);
return "success";
}
資料校驗
JSR 303
- JSR 303 是 Java 為 Bean 資料合法性校驗提供的標準框架,它已經包含在 JavaEE 6.0 中
- JSR 303 通過在 Bean 屬性上標註類似於 @NotNull、@Max 等標準的註解指定校驗規則,並通過標準的驗證介面對 Bean 進行驗證
註解 | 功能說明 |
---|---|
@Null | 被註解的元素必須為null |
@NotNull | 被註解的元素必須不為null |
@AssertTure | 被註解的元素必須為true |
@AssertFalse | 被註解的元素必須為false |
@Min(value) | 被註解的元素必須是一個數字,其值必須大於等於指定的最小值 |
@Max(value) | 被註解的元素必須是一個數字其值必須小於等於指定的最大值 |
@DecimalMin(value) | 被註解的元素必須是一個數字其值必須大於等於指定的最小值 |
@DecimalMax(value) | 被註解的元素必須是一個數字其值必須小於等於指定的最大值 |
@Size(max,min) | 被註解的元素的大小必須在指定的範圍內 |
@Digits(integer,fraction) | 被註解的元素必須是一個數字,其值必須在可接受的範圍內 |
@Past | 被註解的元素必須是一個過去的日期 |
@Future | 被註解的元素必須是一個將來的日期 |
@Pattern(value) | 被註解的元素必須符合指定的正則表示式 |
Hibernate Validator 擴充套件註解
Hibernate Validator 是 JSR 303 的一個參考實現,除支援所有標準的校驗註解外,它還支援以下的擴充套件註解
註解 | 功能說明 |
---|---|
被註解的元素必須是電子郵箱地址 | |
@Length | 被註解的字串的大小必須在指定的範圍內 |
@NotEmpty | 被註解的字串必須非空 |
@Range | 被註解的元素必須在合適的範圍內 |
Spring MVC 資料校驗
- Spring 4.0 擁有自己獨立的資料校驗框架,同時支援 JSR 303 標準的校驗框架。
- Spring 在進行資料繫結時,可同時呼叫校驗框架完成資料校驗工作。在 Spring MVC 中,可直接通過註解驅動的方式進行資料校驗
- Spring 的 LocalValidatorFactroyBean 既實現了 Spring 的 Validator 介面,也實現了 JSR 303 的 Validator 介面。只要在 Spring 容器中定義了一個 LocalValidatorFactoryBean,即可將其注入到需要資料校驗的 Bean 中。
Spring 本身並沒有提供 JSR303 的實現,所以必須將 JSR303 的實現者的 jar 包放到類路徑下。
<mvc:annotation-driven/>
會預設裝配好一個 LocalValidatorFactoryBean,通過在處理方法的入參上標 注 @valid 註解即可讓 Spring MVC 在完成資料繫結後執行資料校驗的工作- 在已經標註了 JSR303 註解的表單/命令物件前標註一個 @Valid,Spring MVC 框架在將請求引數繫結到該入參物件後,就會呼叫校驗框架根據註解宣告的校驗規則實施校驗
Spring MVC 是通過對處理方法簽名的規約來儲存校驗結果的:前一個表單/命令物件的校驗結果儲存到隨後的入參中,這個儲存校驗結果的入參必須是 BindingResult 或 Errors 型別,這兩個類都位於 org.springframework.validation 包中
- 需校驗的 Bean 物件和其繫結結果物件或錯誤物件時成對出現的,它們 之間不允許宣告其他的入參
- Errors 介面提供了獲取錯誤資訊的方法,如 getErrorCount() 或 getFieldErrors(String field)
BindingResult 擴充套件了 Errors 介面
在目標方法中獲取校驗結果
- 在表單/命令物件類的屬性中標註校驗註解,在處理方法對 應的入參前新增 @Valid,Spring MVC 就會實施校驗並將校驗結果儲存在被校驗入參物件之後的 BindingResult 或 Errors 入參中。
- 常用方法:
- FieldError getFieldError(String field)
- List
getFieldErrors() - Object getFieldValue(String field)
- Int getErrorCount()
在頁面上顯示錯誤
- Spring MVC 除了會將表單/命令物件的校驗結果儲存到對應的 BindingResult 或 Errors 物件中外,還會將所有校驗 結果儲存到 “隱含模型”
- 即使處理方法的簽名中沒有對應於表單/命令物件的結果入參,校驗結果也會儲存在 “隱含物件” 中。
- 隱含模型中的所有資料最終將通過 HttpServletRequest 的屬性列表暴露給 JSP 檢視物件,因此在 JSP 中可以獲取錯誤資訊
- 在 JSP 頁面上可通過
顯示錯誤訊息
示例
index.jsp
<form:form action="hello/handle19.action" modelAttribute="user">
<form:errors path="*"></form:errors>
name:<input type="text" name="userName"/>
<form:errors path="userName"></form:errors>
email:<input type="text" name="email"/>
<form:errors path="email"></form:errors>
<input type="submit" value="Submit"/>
</form:form>
User.java
public class User {
@DateTimeFormat(pattern="yyyy/MM/dd")
@Past
private Date birthday;
@NumberFormat(pattern="#,###.##")
@DecimalMax("9999")
@DecimalMin("1000")
private long salary;
@Pattern(regexp="\\w{4,30}")
private String userName;
@Email
private String email;
}
目標方法
@RequestMapping("/handle19")
public String handle19(@Valid @ModelAttribute("user") User user,BindingResult bindingResult) {
if(bindingResult.hasErrors) {
return "forward:/index.jsp"
} else {
System.out.println("驗證通過。。。");
}
user.setId(1000);
System.out.println(user);
return "success"
}
提示訊息的國際化
- 每個屬性在資料繫結和資料校驗發生錯誤時,都會生成一個對應的 FieldError 物件。
- 當一個屬性校驗失敗後,校驗框架會為該屬性生成 4 個訊息程式碼,這些程式碼以校驗註解類名為字首,結合 modleAttribute、屬性名及屬性型別名生成多個對應的訊息程式碼:例如 User 類中的 password 屬性標準了一個 @Pattern 註解,當該屬性值不滿足 @Pattern 所定義的規則時, 就會產生以下 4 個錯誤程式碼:
- Pattern.user.password
- Pattern.password
- Pattern.java.lang.String
- Pattern
當使用 Spring MVC 標籤顯示錯誤訊息時, Spring MVC 會檢視 WEB 上下文是否裝配了對應的國際化訊息,如果沒有,則顯示預設 的錯誤訊息,否則使用國際化訊息。
- 若資料型別轉換或資料格式轉換時發生錯誤,或該有的引數不存在,或呼叫處理方法時發生錯誤,都會在隱含模型中建立錯誤訊息。其錯誤程式碼字首說明如下:
- required:必要的引數不存在。如 @RequiredParam(“param1”) 標註了一個入參,但是該引數不存在
- typeMismatch:在資料繫結時,發生資料型別不匹配的問題
- methodInvocation:Spring MVC 在呼叫處理方法時發生了錯誤
註冊國際化資原始檔
xml <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="i18n"></property> </bean>