SpringMVC-8 資料型別轉換、資料格式化與資料校驗
1. 資料繫結流程
SpringMVC通過反射機制對目標處理方法進行解析,將請求訊息繫結到處理方法的入參中。其中,資料繫結的核心部件是DataBinder,執行機制如下:
資料繫結的具體流程說明如下:
- SpringMVC主框架將ServletRequest物件和目標方法的入參例項傳遞給WebDataBinderFactory例項,以建立DataBinder例項物件;
- DataBinder呼叫裝配在SpringMVC上下文中的ConversionService元件進行資料型別轉換與格式化操作,並將Servlet中的請求資訊填充到入參物件中;
- SpringMVC進而呼叫Validator元件對已經繫結請求訊息的入參物件進行資料合法性校驗,並最終生成資料繫結結果BindingData物件;
- SpringMVC抽取BindingResult中的入參物件和校驗錯誤物件,將其賦給處理方法的響應入參中。
// ModelAttributeMethodProcessor的resolveArgument()方法的核心程式碼:
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
if (binder.getTarget() != null) {
bindRequestParameters(binder, request); //繫結請求資料到入參中
validateIfApplicable(binder, parameter); //校驗入參中資料的合法性
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
}
2. 資料型別轉換
SpringMVC上下文中內建了許多資料型別轉換器,可完成大多數Java型別的換工作,具體可通過除錯模式檢視binder的conversionService屬性值。
3. 自定義資料型別轉換器
明確需求:
// 將表單輸入的name-email-gender-department.id字串轉換為Employee物件
@RequestMapping("/testEmployeeConverter")
public String testEmployeeConverter(Employee employee) {
System.out.println(employee);
return "redirect:/emps";
}
第一步:自定義實現Converter介面的資料型別轉換器類,並新增到Spring的IoC容器中;
package com.qiaobc.springmvc.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import com.qiaobc.springmvc.domain.Department;
import com.qiaobc.springmvc.domain.Employee;
// 三種類型的資料轉換器介面:Converter<S,T>、ConverterFactory、GenericConverter
@Component
public class EmployeeConverter implements Converter<String, Employee>{
@Override
public Employee convert(String source) {
if(source != null) {
String[] strs = source.split("-");
if(strs != null && strs.length == 4) {
String name = strs[0];
String email = strs[1];
String gender = strs[2];
Department department = new Department();
department.setDeptId(Integer.parseInt(strs[3]));
return new Employee(null, name, email, gender, department);
}
}
return null;
}
}
第二步:在SpringMVC的配置檔案中,通過ConversionServiceFactoryBean的converters屬性註冊自定義的型別轉換器;
<!--
配置資料型別轉換器:
1). ConversionService是SpringMVC型別轉換體系的核心介面;
2). 可以利用ConversionServiceFactoryBean在Spring的IoC容器中定義一個ConversionService;
3). Spring將自動識別IOC容器中的ConversionService,並在Bean屬性配置及SpringMVC處理方法入參繫結等場合使用其進行資料型別轉換。
-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
第三步:通過mvc:annotation-driven標籤的conversion-service屬性,將ConversionServiceFactoryBean註冊到SpringMVC的上下文中。
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
4. 關於mvc:annotation-driven
<mvc:annotation-driven/>
會自動註冊RequestMappingHandlerMapping、RequestMappingHandlerAdapter與ExceptionHandlerExceptionResolver三個bean,其還將提供如下支援:
- 支援使用ConversionService例項對錶單引數進行型別轉換;
- 支援使用@NumberFormat和@DateTimeFormat註解完成資料型別的格式化;
- 支援使用@Valid註解對JavaBean例項進行JSR 303驗證;
- 支援使用@RequestBody和@ResponseBody註解。
5. @InitBinder註解
由@InitBinder註解標識的方法,可以對WebDataBinder物件進行初始化;WebDataBinder是DataBinder的子類,用於完成由表單欄位到JavaBean屬性的繫結。注意@InitBinder方法不能有返回值,其入參通常是WebDataBinder物件。
/**
* 在進行資料繫結時,不自動繫結物件中的name屬性
* 注意:其對自定義型別轉換器並不起作用
* @param binder
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("name");
}
6. 資料格式化
6.1 FormattingConversionServiceFactroyBean
主要作用:用於在Spring上下文中構造FormattingConversionService。
其中,FormattingConversionServiceFactroyBean工廠類的內部已經註冊:
- NumberFormatAnnotationFormatterFactroy:支援對數字型別的屬性使用@NumberFormat註解;
- JodaDateTimeFormatAnnotationFormatterFactroy:支援對日期型別的屬性使用@DateTimeFormat註解;
6.2 mvc:annotation-driven
- mvc:annotation-driven預設建立的ConversionService例項即為FormattingConversionServiceFactroyBean;
- 故配置mvc:annotation-driven後即可在SpringMVC入參繫結及模型資料輸出時使用註解驅動,以實現資料型別轉換和資料格式化。
6.3 FormattingConversionService
- 該類是Spring格式化模組中實現了ConversionService介面的實現類;
- 該類擴充套件了GenericConversionService,既具有型別轉換的功能,又具有格式化的功能。
6.4 日期格式化
@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表示完整日期/時間格式、-表示忽略日期或時間格式。
6.5 數值格式化
@NumberFormat註解可對類似數字型別的屬性進行標註,其具有兩個互斥的屬性:
- pattern屬性:字串型別,用於自定義樣式,如”#,###,###.#”;
- style屬性:型別為NumberFormat.Style,用於指定樣式型別,包括Style.NUMBER(正常數字型別)、Style.CURRENCY(貨幣型別)、Style.PERCENT(百分數型別)。
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
@NumberFormat(pattern="###,###,###.##")
private float salary;
7. JSR303資料校驗
JSR 303 是Java為Bean資料合法性校驗提供的標準框架,其已經包含在JavaEE 6.0中;JSR 303 通過在Bean屬性上標註類似於@NotNull、@Max等標準的註解指定校驗規則,並通過標準的驗證介面對Bean進行驗證。
JSR 303 支援的校驗註解如下:
註解 | 功能說明 |
---|---|
@Null | 被註釋的元素必須為null |
@NotNull | 被註釋的元素必須不為null |
@AssertTrue | 被註釋的元素必須為true |
@AssertFalse | 被註釋的元素必須為false |
@Min(value) | 被註釋的元素必須是一個數字,其值必須大於或等於指定的最小值 |
@Max(value) | 被註釋的元素必須是一個數字,其值必須小於或等於指定的最大值 |
@DecimalMin(value) | 被註釋的元素必須是一個數字,其值必須大於或等於指定的最小值 |
@DecimalMax(value) | 被註釋的元素必須是一個數字,其值必須小於或等於指定的最大值 |
@Size(max, min) | 被註釋的元素的大小必須在指定的範圍內 |
@Digits(integer, fraction) | 被註釋的元素必須是一個數字,其值必須在可接受的範圍內 |
@Past | 被註釋的元素必須是一個過去的日期 |
@Future | 被註釋的元素必須是一個將來的日期 |
@Pattern(value) | 被註釋的元素必須符合指定的正則表示式 |
Hibernate Validator 是 JSR 303 的一個參考實現,除支援所有標準的校驗註解外,還支援一下擴充套件註解:
註解 | 功能說明 |
---|---|
被註釋的元素必須是電子郵箱地址 | |
@Length | 被註釋的字串的大小必須在指定的範圍內 |
@NotEmpty | 被註釋的字串必須非空 |
@Range | 被註釋的元素必須在合適的範圍內 |
8. SpringMVC資料校驗
8.1 基本概念
① 關於所需要的jar包
Spring4.0擁有獨立的資料校驗框架,同時支援 JSR 303 標準的校驗框架;但其本身並沒有提供 JSR 303 的實現,故必須將 JSR 303 的實現者的jar包放到類路徑下。
② 關於LocalValidatorFactoryBean工廠類
該工廠類既實現了Spring的Validator介面,也實現了 JSR 303 的Validator介面;故需要在 Spring 容器中定義LocalValidatorFactoryBean,即可將其注入到需要資料校驗的Bean中。
③ 關於@Valid註解
<mvc:annotation-driven/>
會預設裝配LocalValidatorFactoryBean,通過在處理方法的入參上標註@Valid註解即可讓SpringMVC在進行資料繫結時,同時呼叫校驗框架完成資料校驗工作。
④ 關於校驗結果
前一個表單/命令物件的校驗結果儲存到隨後處理方法的入參中,該入參必須是BindingResult或Errors型別;且需注意,需校驗的Bean物件和其繫結結果物件或錯誤物件是成對出現的,其之間不允許宣告其他的入參。
8.2 具體實現
第一步:新增Hibernate Validator驗證框架所依賴的jar包,具體如下圖所示:
第二步:在SpringMVC配置檔案中新增mvc:annotation-driven標籤;
第三步:在需要校驗的JavaBean屬性上新增相應的校驗註解,以Employee為例:
public class Employee {
@NotNull
private String name;
@Email
private String email;
@Past
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
// ……
}
第四步:在處理器目標方法的Bean型別的入參前新增@Valid註解,並新增儲存校驗結果的物件:
@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map) {
System.out.println(employee);
if(result.getErrorCount() > 0) {
for(FieldError error : result.getFieldErrors()) {
System.out.println(error.getField() + " : " + error.getDefaultMessage());
}
// 指定校驗錯誤時所轉向的定製頁面
map.put("departments", departmentDao.getDepartments());
return "emp-edit";
}
employeeDao.save(employee);
return "redirect:/emps";
}
9. 校驗錯誤訊息的顯示
SpringMVC還會將所有校驗結果儲存到隱含模型中,該模型中的所有資料最終將通過HttpServletRequest的屬性列表暴露給JSP檢視物件,故在JSP頁面上可以獲取錯誤資訊。
<form:form action="${pageContext.request.contextPath }/emp" method="post" modelAttribute="employee">
<!-- 顯示所有錯誤訊息 -->
<form:errors path="*"></form:errors><br><br>
Name:<form:input path="name"/>
<!-- 在指點欄位後顯示當前欄位的錯誤訊息 -->
<form:errors path="name"></form:errors>
<br><br>
Email:<form:input path="email"/>
<form:errors path="email"></form:errors>
<br><br>
Birthday:<form:input path="birth"/>
<form:errors path="birth"></form:errors>
<br><br>
<input type="submit" name="submit">
</form:form>
10. 校驗錯誤訊息的國際化
當某屬性校驗失敗後,SpringMVC校驗框架會為該屬性生成4個訊息程式碼,其以校驗註解類名為字首,結合modelAttribute、屬性名及屬性型別名;如NotNull.employee.name、NotNull.name、NotNull.java.lang.String和NotNull。
錯誤程式碼字首除了校驗註解類名外,還有如下幾種:
錯誤程式碼字首 | 具體說明 |
---|---|
required | 必要的引數不存在;如@RequiredParam(“name”)標註入參,但該引數不存在時發生的錯誤 |
typeMismatch | 在資料繫結時,發生資料型別轉換或格式化錯誤 |
methodInvocation | SpringMVC在呼叫處理方法時發生錯誤 |
具體實現步驟:
第一步:建立國際化資原始檔i18n.properties,檔案內容如下所示:
NotNull.employee.name=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A
Email.employee.email=\u7535\u5B50\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF
Past.employee.birth=\u51FA\u751F\u65E5\u671F\u4E0D\u80FD\u662F\u5C06\u6765\u65F6\u95F4
第二步:在SpringMVC配置檔案中註冊國際化資原始檔,具體如下:
<!-- 配置國際化資原始檔 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
說明:當使用SpringMVC標籤顯示錯誤訊息時,SpringMVC先檢視WEB上下文是否裝配對應的國際化訊息,若有則顯示國際化訊息,否則顯示預設的錯誤訊息。