一、數據校驗
項目中涉及到數據校驗,如果只做前端校驗是不安全的,我們可以繞過前端校驗,重要數據一定要加上后端校驗;
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.0,Tomcat7.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 來讀取國際化信息。