Spring MVC 資料轉換 & 資料格式化 & 資料校驗
資料綁定流程
- Spring MVC 主架構将 ServletRequest 對象及目标方法的入參執行個體傳遞給 WebDataBinderFactory 執行個體,以建立DataBinder 執行個體對象
- DataBinder 調用裝配在 Spring MVC 上下文中的ConversionService 元件進行資料類型轉換、資料格式化工作。将 Servlet中的請求資訊填充到入參對象中
- 調用 Validator 元件對已經綁定了請求消息的入參對象 進行資料合法性校驗,并最終生成資料綁定結果BindingData 對象
- Spring MVC 抽取 BindingResult 中的入參對象和校驗錯誤對象,将它們賦給處理方法的響應入參
Spring MVC 通過反射機制對目标處理方法進行解析,将請求消息綁定到處理方法的入參中。資料綁定的核心部件是DataBinder,運作機制如下:
1 資料轉換
Spring MVC 上下文中内建了很多轉換器,可完成大多數 Java 類型的轉換工作。
ConversionService converters =
自定義類型轉換器
- ConversionService 是 Spring 類型轉換體系的核心接口。
- 可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定義一個 ConversionService. Spring 将自動識别出IOC 容器中的 ConversionService,并在 Bean 屬性配置及Spring MVC 處理方法入參綁定等場合使用它進行資料的轉換
- 可通過 ConversionServiceFactoryBean 的 converters 屬性注冊自定義的類型轉換器
<!-- 配置 ConversionService -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</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-service="conversionService"></mvc:annotation-driven>
<!-- 配置 ConversionService -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
@RequestMapping("/testConversionServiceConverer")
public String testConverter(@RequestParam("employee") Employee employee){
System.out.println("save: " + employee);
employeeDao.save(employee);
return "redirect:/emps";
}
關于 mvc:annotation-driven
⚫<mvc:annotation-driven /> 會自動注冊RequestMappingHandlerMapping、RequestMappingHandlerAdapter 與ExceptionHandlerExceptionResolver 三個bean。
還将提供以下支援:
- 支援使用 ConversionService 執行個體對表單參數進行類型轉換
- 支援使用 @NumberFormat annotation、@DateTimeFormat注解完成資料類型的格式化
- 支援使用 @Valid 注解對 JavaBean 執行個體進行 JSR 303 驗證
- 支援使用 @RequestBody 和 @ResponseBody 注解
@InitBinder
- 由 @InitBinder 辨別的方法,可以對 WebDataBinder 對 象進行初始化。WebDataBinder 是 DataBinder 的子類,用于完成由表單字段到 JavaBean 屬性的綁定
- @InitBinder方法不能有傳回值,它必須聲明為void。
- @InitBinder方法的參數通常是是 WebDataBinder
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("lastname");
}
2 資料格式化
- 對屬性對象的輸入/輸出進行格式化,從其本質上講依然屬于 “類型轉換” 的範疇。
- 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="#,###";
public class Employee {
@Past
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
@NumberFormat(pattern="#,###,###.#")
private Float salary;
}
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String save(Employee employee) {
employeeDao.save(employee);
System.out.println("save: " + employee);
return "redirect:/emps";
}
3 資料校驗 JSR 303
⚫JSR 303 是 Java 為 Bean 資料合法性校驗提供的标準架構,它已經包含在 JavaEE 6.0 中 .
⚫JSR 303 通過在 Bean 屬性上标注類似于 @NotNull、@Max 等标準的注解指定校驗規則,并通過标準的驗證接口對 Bean 進行驗證
Hibernate Validator 擴充注解
Hibernate Validator 是 JSR 303 的一個參考實作,除支援所有标準的校驗注解外,它還支援以下的擴充注解
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<FieldError> getFieldErrors()
- Object getFieldValue(String field)
- Int getErrorCount()
在頁面上顯示錯誤
- Spring MVC 除了會将表單/指令對象的校驗結果儲存到對 應的 BindingResult 或 Errors 對象中外,還會将所有校驗 結果存到 “隐含模型”
- 即使處理方法的簽名中沒有對應于表單/指令對象的結果入參,校驗結果也會儲存在 “隐含對象” 中。
- 隐含模型中的所有資料最終将通過 HttpServletRequest 的屬性清單暴露給 JSP 視圖對象,是以在 JSP 中可以擷取錯誤資訊
- 在 JSP 頁面上可通過 <form:errors path=“userName”> 顯示錯誤消息
Birth: <form:input path="birth" />
<form:errors path="birth"></form:errors>
<br>
Salary: <form:input path="salary" />
<br>
public class Employee {
private Integer id;
@NotEmpty
private String lastName;
@Email
private String email;
//1 male, 0 female
private Integer gender;
private Department department;
@Past
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
@NumberFormat(pattern="#,###,###.#")
private Float salary;
}
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String save(@Valid Employee employee, Errors result, Map<String, Object> map) {
System.out.println("save: " + employee);
if (result.getErrorCount() > 0) {
System.out.println("出錯了!");
for (FieldError error : result.getFieldErrors()) {
System.out.println(error.getField() + ":" + error.getDefaultMessage());
}
// 若驗證出錯, 則轉向定制的頁面
map.put("departments", departmentDao.getDepartments());
return "input";
}
employeeDao.save(employee);
return "redirect:/emps";
}
提示消息的國際化
⚫每個屬性在資料綁定和資料校驗發生錯誤時,都會生成一個對應的 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在調用處理方法時發生了錯誤
⚫注冊國際化資源檔案
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>