第五節:SpringMVC 數據校驗


一、數據校驗

  項目中涉及到數據校驗,如果只做前端校驗是不安全的,我們可以繞過前端校驗,重要數據一定要加上后端校驗;

  1、通過程序,每個數據取出,進行校驗,如果失敗直接來到添加頁面,提示其重新填寫;(不推薦)

  2、SpringMVC:可以利用 JSR303 做數據校驗;

 

二、如何校驗

  1、JSR 303 

    JSR 303 是 Java 為 Bean 數據合法性校驗提供的標准框架,它已經包含在 JavaEE 6.0 中 。

    JSR 303 (Java Specification Requests意思是Java 規范提案)通過在 Bean 屬性上標注類似於 @NotNull、@Max 等標准的注解指定校驗規則,並通過標准的驗證接口對 Bean 進行驗證:

    

 

 

 

  2、Hibernate Validator 擴展注解

    Hibernate Validator 是 JSR 303 的一個參考實現,除支持所有標准的校驗注解外,它還支持以下的擴展注解:

    

 

 

 

  3、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 框架在將請求參數綁定到該入參對象后,就會調用校驗框架根據注解聲明的校驗規則實施校驗;

 

三、實現數據校驗

  1、使用JSR 303驗證標准

  2、加入hibernate validator驗證框架

  3、在SpringMVC配置文件中增加< mvc:annotation-driven />

  4、需要在bean的屬性上增加對應驗證的注解

  5、在目標方法bean類型的前面增加@Valid注解

四、實驗代碼

  1、添加依賴

    Maven 方式:

            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>5.2.4.Final</version>
            </dependency>

 

    jar 包方式:

hibernate-validator-5.0.0.CR2.jar hibernate-validator-annotation-processor-5.0.0.CR2.jar classmate-0.8.0.jar jboss-logging-3.1.1.GA.jar validation-api-1.1.0.CR1.jar

 

    注意:推薦使用 Tomcat7.0Tomcat7.0以上的 el 表達式比較強大;

  2、給驗證屬性上增加驗證注解

public class Employee { private Integer id; @NotBlank @NotEmpty @Length(min = 6, max = 10, message = "姓名超出長度限制") private String lastName; @NotBlank @Email @Length(max = 15, message = "電子郵件超出長度限制") private String email; private Integer gender; //可以規定頁面提交的日期格式
    @DateTimeFormat(pattern = "yyyy-MM-dd") @Past //必須是一個過去的時間 //@Future //必須是一個未來的時間
    private Date birth; //假設頁面,為了顯示方便提交的工資 10000.98,---》 ¥10,000.98
    @NumberFormat(pattern="#,###,###.##") private Double salary; private Department department; }

 

  3、在SpringMVC封裝對象的時候,告訴SpringMVC這個 JavaBean 需要校驗

    /** * 保存員工 * 增加@Valid注解,驗證失敗會報錯。 * @param employee * @return
     */ @RequestMapping(value = "/emp", method = RequestMethod.POST) public String addEmp(@Valid Employee employee) { System.out.println("employee = " + employee); return "success"; }

 

  4、如何知道校驗結果?

  給需要校驗的 JavaBean 后面緊跟一個 BindingResult,這個BindingResult 就是封裝了前一個Bean的校驗結果而且不能間隔任何參數。

    /** * 保存員工 * 增加@Valid注解,驗證失敗會報錯。 * @param employee * @return
     */ @RequestMapping(value = "/emp", method = RequestMethod.POST) public String addEmp(@Valid Employee employee, BindingResult result) { System.out.println("employee = " + employee); //是否有校驗錯誤
        if (result.hasErrors()) { System.out.println("有校驗錯誤!!!"); return "add"; } else { //employeeDao.save(employee);
            System.out.println("保存成功!"); return "success"; } }

 

    根據不同的校驗結果然后決定如何操作

 

五、錯誤信息的顯示

  1、關於錯誤信息

public interface BindingResult extends Errors

  

  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()

 

 

  2、使用表單標簽回顯錯誤結果

    Spring MVC 除了會將表單/命令對象的校驗結果保存到對應的 BindingResult 或 Errors 對象中外,還會將所有校驗結果保存到 “隱含模型”;

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

    隱含模型中的所有數據最終將通過 HttpServletRequest 的屬性列表暴露給 JSP 視圖對象,因此在 JSP 中可以獲取錯誤信息

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

    使用 <form:errors> 標簽可以顯示該屬性的錯誤提示:

<form:errors path="屬性"/>

     示例:

    在表單上頁面上顯示所有的錯誤消息:

<!-- 顯示所有的錯誤消息 -->
<form:errors path="*"/>

  

    顯示某一個表單域的錯誤消息:

<form:errors  path="lastName"/>  

  

    顯示校驗的提示信息:

<form:form action="${ctx}/emp" modelAttribute="employee" method="POST"> lastName:<form:input path="lastName" /> <form:errors path="lastName"/> <br/> email:<form:input path="email" /> <form:errors path="email"/><br/> gender:<br><form:radiobutton path="gender" value="1"/><br/><form:radiobutton path="gender" value="0" /><br/> birth:<form:input path="birth" /> <form:errors path="birth"/><br/> salary:<form:input path="salary" /><br/> dept: <form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id"></form:select>
    <br>
    <input type="submit" value="保存">
</form:form>

 

  3、將校驗信息回寫

    BindingResult 中封裝了類型轉換與格式化的錯誤信息,可以把錯誤信息放在請求域中,帶回給頁面。

    示例:

    /** * 保存員工 * 增加@Valid注解,驗證失敗會報錯。 * @param employee * @return
     */ @RequestMapping(value = "/emp", method = RequestMethod.POST) public String addEmp(@Valid Employee employee, BindingResult result, Model model) { System.out.println("employee = " + employee); Map<String, Object> errorsMap = new HashMap<>(); //是否有校驗錯誤
        if (result.hasErrors()) { System.out.println("有校驗錯誤!!!"); List<FieldError> errors = result.getFieldErrors(); for (FieldError fieldError : errors) { System.out.println("錯誤消息提示:" + fieldError.getDefaultMessage()); //錯誤信息
                System.out.println("錯誤的字段是:" + fieldError.getField());          //錯誤字段
 System.out.println(fieldError); errorsMap.put(fieldError.getField(), fieldError.getDefaultMessage()); } model.addAttribute("errorInfo", errorsMap); return "add"; } else { //employeeDao.save(employee);
            System.out.println("保存成功!"); 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 在調用處理方法時發生了錯誤

  

  注冊國際化資源文件

  

 

 

   國際化資源文件怎么寫呢?對應的 key 是什么?

  每一個字段發生錯誤以后,都會有自己的錯誤代碼;國際化文件中錯誤消息的 key 必須對應一個錯誤代碼;

  錯誤信息:

Field error in object 'employee' on field 'email': rejected value [aa];
codes [Email.employee.email,Email.email,Email.java.lang.String,Email];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable:
codes [employee.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@4e69aec9,.*];
default message [不是一個合法的電子郵件地址]

  codes:

Email.employee.email        校驗規則(@Email).隱含模型中這個對象的key.對象的屬性
Email.email                 校驗規則.屬性名
Email.java.lang.String      校驗規則.屬性類型
Email                       校驗規則

  

  說明:

1、如果隱含模型中 employee 對象的 email 字段發生了 @Email 校驗錯誤,就會生成 Email.employee.email;
2、Email.email:所有的 email 屬性只要發生了  @Email 校驗錯誤;
3、Email.java.lang.String:只要是 String 類型發生了 @Email 校驗錯誤;
4、Email:只要發生了 @Email 校驗錯誤;

   注意:如果根據 codes 配置了多個值,精確匹配的 codes 優先生效;

六、提示消息的國際化實驗

  1、定義國際化資源文件

    中文:errors_zh_CN.properties

Email.email=郵箱錯誤!! Email=郵箱不正確!! NotEmpty=不允許為空!! Length.java.lang.String=長度錯誤!! Past=必須是一個過去時間!! typeMismatch.employee.birth=生日格式不正確!! typeMismatch=格式不正確!! Length=長度錯誤!!

 

    英文:errors_en_US.properties

Email.email=email incorrect~~ Email=email error~~ NotEmpty=must not empty~~ Length.java.lang.String=length incorrect {0},must between {2} and {1}~~ Past=must a past time~~ typeMismatch.employee.birth=birthday incorrect~~ typeMismatch=formatting incorrect~~ Length=length incorrect~~

 

  2、聲明國際化資源配置(讓SpringMVC來管理國際化資源文件)

    <!--管理國際化資源文件-->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="errors"/>
    </bean>

 

  3、錯誤信息國際化回顯

<form:form action="${ctx}/emp" modelAttribute="employee" method="POST"> lastName:<form:input path="lastName" />
            <form:errors path="lastName"/> --->  ${errorInfo.lastName}<br/> email:<form:input path="email" />
            <form:errors path="email"/> ---> ${errorInfo.email}<br/> gender:<br><form:radiobutton path="gender" value="1"/><br/><form:radiobutton path="gender" value="0" /><br/> birth:<form:input path="birth" />
            <form:errors path="birth"/> ---> ${errorInfo.birth}<br/> salary:<form:input path="salary" /><br/> dept: <form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id"></form:select>
    <br>
    <input type="submit" value="保存">
</form:form>

 

    格式或者校驗錯誤后,<form:erros path=""> 就會顯示定制的國際化信息。

 

  4、高級國際化

    如何在國際化文件中動態的傳入參數信息?

    示例:

Length.java.lang.String=length incorrect {0},must between {2} and {1}~~

     說明:

{0} 永遠都是當前屬性名
{1} {2} 才是參數信息,並且是按照屬性大小寫排序的順序

  

 

  5、message 指定錯誤消息

     注釋上有個屬性是message,直接寫上錯誤信息即可,缺點不能國際化

    例如:

 @NotBlank @NotEmpty @Length(min = 6, max = 10, message = "姓名超出長度限制") private String lastName; @NotBlank @Email @Length(max = 15, message = "電子郵件超出長度限制") private String email; @DateTimeFormat(pattern = "yyyy-MM-dd") @Past //必須是一個過去的時間 //@Future //必須是一個未來的時間
    private Date birth;

 

  6、國際化信息的回顯

    根據錯誤信息,然后通過國際化文件,把信息回顯到頁面

    /** * 保存員工 * 增加@Valid注解,驗證失敗會報錯。 * @param employee * @return
     */ @RequestMapping(value = "/emp", method = RequestMethod.POST) public String addEmp(@Valid Employee employee, BindingResult result, Model model, @RequestHeader("Accept-Language") String language) { Locale locale = getLocale(language); System.out.println("locale = " + locale); System.out.println("employee = " + employee); Map<String, Object> errorsMap = new HashMap<>(); //是否有校驗錯誤
        if (result.hasErrors()) { System.out.println("有校驗錯誤!!!"); List<FieldError> errors = result.getFieldErrors(); for (FieldError fieldError : errors) { System.out.println("錯誤消息提示:" + fieldError.getDefaultMessage()); //錯誤信息
                System.out.println("錯誤的字段是:" + fieldError.getField());          //錯誤字段
 System.out.println(fieldError); System.out.println(fieldError.getCode()); //Length
                System.out.println(Arrays.toString(fieldError.getCodes())); //[Length.employee.lastName, Length.lastName, Length.java.lang.String, Length]
                String message = messageSource.getMessage(fieldError.getCode(), new Object[0], locale); System.out.println("message = " + message); errorsMap.put(fieldError.getField(), fieldError.getDefaultMessage()); } model.addAttribute("errorInfo", errorsMap); return "add"; } else { //employeeDao.save(employee);
            System.out.println("保存成功!"); return "success"; } } private Locale getLocale(String language) { System.out.println("language = " + language); Locale locale = null; if (!StringUtils.isEmpty(language)) { //locale = en_US,EN;Q=0.9,ZH
            String[] split = language.split("-"); locale = new Locale(split[0], split[1]); } System.out.println("locale = " + locale); return locale; }

 

 

  錯誤信息都封裝在 FieldError 對象里,其中有 getCodes() 和 getCode() 方法,分別用於獲取所有的 codes 值和最后一個 code 值:

  

   同時把國際化文件 MessageSource 注入進來,然后根據請求頭的語言信息和 code 來讀取國際化信息。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM