1. 程式人生 > >SpringMVC-8 資料型別轉換、資料格式化與資料校驗

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 的一個參考實現,除支援所有標準的校驗註解外,還支援一下擴充套件註解:

註解 功能說明
@Email 被註釋的元素必須是電子郵箱地址
@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上下文是否裝配對應的國際化訊息,若有則顯示國際化訊息,否則顯示預設的錯誤訊息。