1. 程式人生 > >SpringMVC的資料轉換、格式化和資料校驗

SpringMVC的資料轉換、格式化和資料校驗

目錄

5.案例

一、資料轉換

  • Spring MVC 上下文中內建了很多轉換器,可完成大多數 Java 型別的轉換工作。

1.ConversionService

  • ConversionService 是 Spring 型別轉換體系的核心介面

  • 可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC容器中定義一個 ConversionService. Spring 將自動識別出IOC 容器中的 ConversionService,並在 Bean 屬性配置及Spring MVC 處理方法入參繫結等場合使用它進行資料的轉換

  • 可通過 ConversionServiceFactoryBean 的 converters 屬性註冊自定義的型別轉換器

  • Spring 定義了 3 種類型的轉換器介面,實現任意一個轉換器介面都可以作為自定義轉換器註冊到ConversionServiceFactroyBean 中

    • Converter<S,T>:將 S 型別物件轉為 T 型別物件

    • ConverterFactory:將相同系列多個 “同質” Converter 封裝在一 起。如果希望將一種型別的物件轉換為另一種型別及其子類的物件(例如將 String 轉換為 Number 及 Number 子類(Integer、Long、Double 等)物件)可使用該轉換器工廠類

    • GenericConverter:會根據源類物件及目標類物件所在的宿主類中的上下文資訊進行型別轉換

2.自定義型別轉換器

(1)案例一

  • 按照001,開發部這種格式將字串轉換為Department物件

Department.java

public class Departement {
    private Integer id;
    private  String name;
}

StringToDeptConverter.java

package com.itheima.converter;
​
import com.itheima.domain.Departement;
import org.springframework.core.convert.converter.Converter;
​
public class StringToDeptConverter implements Converter<String, Departement> {
​
    @Override
    public Departement convert(String s) {
        Departement departement = new Departement();
        //001,開發部
        String[] split = s.split(",");
        if (split != null && split.length != 0) {
            departement.setId(Integer.parseInt(split[0]));
            departement.setName(split[1]);
        }
        System.out.println("轉換成功");
        return departement;
    }
}

dispatcher-servlet.xml

    <!-- 裝配自定義的型別轉換器 -->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
    <!-- 自定義的型別轉換器 -->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.itheima.converter.StringToDeptConverter"></bean>
            </list>
        </property>
    </bean>

UserController.java

    @RequestMapping("/{formName}")
    public String loginForm(@PathVariable String formName) {
        //動態跳轉頁面
        return formName;
    }
​
    @RequestMapping(value = "/ConverterTest", method = RequestMethod.POST)
    public String ConverterTest(@RequestParam Departement department,Model model) {
        model.addAttribute("department",department);
        return "success";
    }

test.jsp

<form action="/ConverterTest" method="post">
  <!-- 輸入xxx,xx格式的字串 eg:001,開發部 -->
    請輸入:<input  type="text" name="department"><br/>
    <input type="submit" value="登陸"/><br/>
</form>
  • 在dispatcher-servlet.xml配置檔案中,使用了mvc:annotation-driven/標籤,該標籤會自動註冊RequestMappingHandlerMapping與RequestMappingHandlerAdapter兩個bean,這是SpringMVC為@Controller分發請求所必須的

  • 除此之外,該標籤還會註冊一個預設的ConverService,即FormattingConversionServiceFactoryBean。如果需要使用自定義的ConverService轉換類,需要顯示定義一個ConverService來覆蓋之前的預設實現類。

(2)案例二

  • 將String格式轉換為Date格式

user.java

public class User {
    private String loginname;
    private Date birthday;
}

StringToDateConverter.java

package com.itheima.converter;
​
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
​
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
​
@Component
public class StringToDateConverter implements Converter<String, Date> {
    private String datePattern;
​
    public void setDatePattern(String datePattern) {
        this.datePattern = datePattern;
    }
​
    @Override
    public Date convert(String s) {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat(this.datePattern);
            return dateFormat.parse(s);
        } catch (ParseException e) {
            e.printStackTrace();
            System.out.println("日期轉換失敗");
            return null;
        }
    }
}

dispatcher-servlet.xml

    <!-- 裝配自定義的型別轉換器 -->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
    <!-- 自定義的型別轉換器 -->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.itheima.converter.StringToDateConverter">
                    <property name="datePattern" value="yyyy-MM-dd"></property>
                </bean>
                <bean class="com.itheima.converter.StringToDeptConverter"></bean>
            </list>
        </property>
    </bean>

UserController.java

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String register(@ModelAttribute User user, Model model) {
        System.out.println("register...");
        model.addAttribute("user", user);
        return "success";
    }

test.jsp

<form action="register" method="post">
    登入名:<input type="text" id="loginname" name="loginname" /><br/>
    生日:<input type="text" id="birthday" name="birthday"/><br/>
    <input type="submit" value="登陸"/><br/>
</form>

二、處理靜態資源

  • 優雅的 REST 風格的資源URL 不希望帶 .html 或 .do 等字尾

  • 若將 DispatcherServlet 請求對映配置為 /,則 Spring MVC 將捕獲WEB 容器的所有請求,包括靜態資源的請求, SpringMVC 會將他們當成一個普通請求處理,因找不到對應處理器將導致錯誤。

    • mvc:default-servlet-handler/ 將在 SpringMVC 上下文中定義一個 DefaultServletHttpRequestHandler,它會對進入 DispatcherServlet 的請求進行篩查,如果發現是沒有經過對映的請求,就將該請求交由 WEB 應用伺服器預設的 Servlet 處理,如果不是靜態資源的請求,才由DispatcherServlet 繼續處理

  • 一般 WEB 應用伺服器預設的 Servlet 的名稱都是 default。若所使用的WEB 伺服器的預設 Servlet 名稱不是 default,則需要通過 default-servlet-name 屬性顯式指定

    <!-- 預設使用基於註釋的介面卡和對映器 -->
    <mvc:annotation-driven/>
    <!-- 只把動態資訊當做controller處理,忽略靜態資訊 -->
    <mvc:default-servlet-handler/>

三、關於 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 dataBinder){
  dataBinder.setDisallowedFields("roleSet");
}

七、資料格式化

  • Spring 在格式化模組中定義了一個實現ConversionService 介面的FormattingConversionService 實現類,該實現類擴充套件了 GenericConversionService,因此它既具有型別轉換的功能,又具有格式化的功能

  • FormattingConversionService 擁有一個FormattingConversionServiceFactroyBean 工廠類,後者用於在 Spring 上下文中構造前者

  • FormattingConversionServiceFactroyBean內部已經註冊了 :

    • NumberFormatAnnotationFormatterFactroy:支援對數字型別的屬性使用 @NumberFormat 註解

    • JodaDateTimeFormatAnnotationFormatterFactroy:支援對日期– 型別的屬性使用 @DateTimeFormat 註解

  • 裝配了 FormattingConversionServiceFactroyBean 後,就可以在 Spring MVC 入參繫結及模型資料輸出時使用註解驅動了。mvc:annotation-driven/ 預設建立的ConversionService 例項即為FormattingConversionServiceFactroyBean

1.日期格式化

  • @DateTimeFormat 註解可對java.util.Date、java.util.Calendar、java.long.Long 時間型別進行標註

    • pattern 屬性:型別為字串。指定解析/格式化欄位資料的模式,如:”yyyy-MM-dd hh:mm:ss”

    • iso 屬性:型別為 DateTimeFormat.ISO。指定解析/格式化欄位資料的ISO模式

    • style 屬性:字串型別。通過樣式指定日期時間的格式,由兩位字元組成,第一位表示日期的格式,第二位表示時間的格式:S:短日期/時間格式、M:中日期/時間格式、L:長日期/時間格式、F:完整日期/時間格式、-:忽略日期或時間格式

    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birth; 

2.數值格式化

  • @NumberFormat可對類似數字型別的屬性進行標註,它擁有兩個互斥的屬性:

    • style:型別為 NumberFormat.Style。用於指定– 樣式型別,包括三種:Style.NUMBER(正常數字型別)、Style.CURRENCY(貨幣型別)、 Style.PERCENT(百分數型別)

    • pattern:型別為 String,自定義樣式,如patter="#,###";

    @NumberFormat(pattern="#,###,###.#")
    private Float salary;

八、資料校驗

1.JSR 303

  • JSR 303 是 Java 為 Bean 資料合法性校驗提供的標準框架,它已經包含在 JavaEE 6.0 中

  • JSR 303 通過在 Bean 屬性上標註類似於 @NotNull、@Max等標準的註解指定校驗規則,並通過標準的驗證介面對 Bean 進行驗證

2.Hibernate Validator拓展註解

  • Hibernate Validator 是 JSR 303 的一個參考實現,除支援所有標準的校驗註解外,它還支援以下的擴充套件註解

3.Spring MVC資料校驗

  • Spring 4.0 擁有自己獨立的資料校驗框架,同時支援 JSR303 標準的校驗框架

  • 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 介面

4.在目標方法中獲取校驗結果

  • 在表單/命令物件類的屬性中標註校驗註解,在處理方法對應的入參前新增 @Valid,Spring MVC 就會實施校驗並將校驗結果儲存在被校驗入參物件之後的 BindingResult 或Errors 入參中。

  • 常用方法:

    • FieldError getFieldError(String field)

    • List<FieldError> getFieldErrors()

    • Object getFieldValue(String field)

    • Int getErrorCount()

5.在頁面上顯示錯誤

  • Spring MVC 除了會將表單/命令物件的校驗結果儲存到對應的 BindingResultErrors 物件中外,還會將所有校驗結果儲存到 “隱含模型”

  • 即使處理方法的簽名中沒有對應於表單/命令物件的結果入參,校驗結果也會儲存在 “隱含物件” 中

  • 隱含模型中的所有資料最終將通過 HttpServletRequest的屬性列表暴露給 JSP 檢視物件,因此在 JSP 中可以獲取錯誤資訊

  • 在 JSP 頁面上可通過 <form:errors path=“userName”>顯示錯誤訊息

5.案例

匯入相關jar包

user.java

public class User {
    @NotBlank(message="登入名不能為空")
    private String loginname;
    @Length(min=6,max=8,message = "密碼長度必須在6-8位")
    private String password;
    @NotBlank(message = "使用者名稱不能為空")
    private String username;
    @Range(min=10,max = 100,message = "年齡必須在10-100歲之間")
    private Integer age;
​
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Past(message = "生日必須是一個過去的日期")
    private Date birthday;
    @Email(message = "必須是合法的郵箱地址")
    private  String email;
    @Pattern(regexp = "[1][3,8][3,6,9][0-9]{8}",message = "無效的電話號碼")
    private String phone;

UserController.java

@Controller
public class UserController {
​
    @RequestMapping("/{formName}")
    public String loginForm(@PathVariable String formName,Model model) {
        model.addAttribute("user",new User1());
        //動態跳轉頁面
        return formName;
    }
    @RequestMapping(value="/login", method=RequestMethod.POST)
    public String save(@Valid @ModelAttribute User user, Errors result,
                       Map<String, Object> map){
        System.out.println("save: " + user);
​
        if(result.getErrorCount() > 0){
            System.out.println("出錯了!");
​
            for(FieldError error:result.getFieldErrors()){
                System.out.println(error.getField() + ":" + error.getDefaultMessage());
            }
​
            //若驗證出錯, 則轉向定製的頁面
            map.put("user", user);
            return "registerForm";
        }
​
        return "success";
    }
}

registerForm.jsp

<form:form modelAttribute="user" method="post" action="login">
    帳號:<form:input path="loginname"/>
    <form:errors path="loginname" cssStyle="color:red"/>
    <br/>
    密碼:<form:password path="password"/>
    <form:errors path="password" cssStyle="color:red"/>
    <br/>
    使用者名稱:<form:input path="username"/>
    <form:errors path="username" cssStyle="color:red"/>
    <br/>
    年齡:<form:input path="age"/>
    <form:errors path="age" cssStyle="color:red"/>
    <br/>
    郵箱:<form:input path="email"/>
    <form:errors path="email" cssStyle="color:red"/>
    <br/>
    生日:<form:input path="birthday"/>
    <form:errors path="birthday" cssStyle="color:red"/>
    <br/>
    電話:<form:input path="phone"/>
    <form:errors path="phone" cssStyle="color:red"/>
    <br/>
    <input type="submit" value="提交">
</form:form>

success.jsp

<h3>測試JSR 303</h3>
登入名:${requestScope.user.loginname}<br/>
密碼:${requestScope.user.password}<br/>
使用者名稱:${requestScope.user.username}<br/>
年齡:${requestScope.user.age}<br/>
郵箱:${requestScope.user.email}<br/>
生日:${requestScope.user.birthday}<br/>
電話:${requestScope.user.phone}<br/>