Web系統(tǒng)中甥绿,校驗是必不可少的環(huán)節(jié)嵌戈,校驗一般分為前端校驗和后端校驗,前端校驗一般使用腳本語言搂誉,對即將要提交的數據進行校驗徐紧,不符合業(yè)務要求的將給予提示。后端校驗一般是邏輯性校驗,例如校驗用戶的某種憑證是否過期并级,某種參數是否在合法請求范圍內等在前端不方便的校驗拂檩。
設涉知識點:
- Bean Validation數據校驗
- 分組校驗
- Spring Validation接口校驗
相關jar包:
- hibernate-validator-4.3.0.Final.jar
- jobs-logging-3.1.0.CR2.jar
- validation-api-1.0.0.GA.jar
1. Bean Validation數據校驗
特性: 使用簡潔的注釋語法來對Bean中的某個屬性進行校驗。
如:看一下前臺傳來的水果商品參數中的水果名稱是否長度超限死遭,產地信息是否為空广恢。
內容接著:SpringMVC[2]--框架搭建
1.1 搭建validation校驗框架
- 在annotation-driven的注解驅動配置上添加一個validator屬性,為其指定一個“validator”值呀潭,該值為“校驗器”的名稱钉迷,配置如下:
<mvc:annotation-driven validator="validator"/>
- 在核心配置文件springmvc.xml中添加名為“validator”的校驗器配置,其具體配置如下:
<!--校驗器-->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name="validationMessageSource" ref="messageSource"/>
</bean>
?定義了一個為“validator”的校驗器钠署,指定其中的校驗器提供類是“HibernateValidator”糠聪,即添加的Hibernate校驗器。
?而下面的validationMessageSource指的是校驗使用的資源文件谐鼎,在該文件中配置校驗的錯誤信息舰蟆。若不配置默認使用classpath下的ValidationMessages.properties。
在springmvc.xml中添加id為messageSource的資源屬性文件配置:
<!--校驗錯誤信息配置文件-->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:ProductValidationMessages.properties</value>
</list>
</property>
<!--文件格式設置為“utf-8”-->
<property name="fileEncodings" value="utf-8"/>
<!--內容緩存時間設置為120s-->
<property name="cacheSeconds" value="120"/>
</bean>
然后需要在config或者resources文件夾中新建ProductValidationMessages.properties配置文件狸棍,用來配置校驗錯誤信息身害。
- 由于該校驗機制是給處理器Controlelr使用的,而加載和調用處理器的是處理器適配器HandlerAdapter草戈,所以要為處理器適配器的配置添加校驗器:
<mvc:annotation-driven conversion-service="conversionService" validator="validator"/>
【下面的內容<mvc:annotation-driven/>會自動默認注冊塌鸯,所以不用寫√破】
- validator還需要檢測前臺傳來的日期丙猬、數字類型數據是否正確,所以在其conversion-service屬性中配置一個可以將字符串轉換為Data類型或數字類型的Java類费韭,配置如下:
<!--檢測前臺傳來的日期茧球、數字類型數據是否正確-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"/>
1.2 添加校驗注解信息
package cn.com.mvc.model;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Size;
public class Fruits {
@Size(min=1, max=20, message="{fruits.name.length.error}")
private String name;
private double price;
@NotEmpty(message="{fruits.producing_area.isEmpty}")
private String producing_area;//產地
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getProducing_area() {
return producing_area;
}
public void setProducing_area(String producing_area) {
this.producing_area = producing_area;
}
}
?在name屬性上添加@Size注解,并且指定了其最行浅帧(min)和最大(max)字符限制抢埋,其中message用來提示校驗出錯誤時顯示的錯誤信息。
?校驗非空使用的注解為@NotEmpty钉汗,其中也指定了message錯誤信息羹令。
- 編寫ProductValidationMessages.properties配置文件
#添加校驗錯誤提示信息
fruits.name.length.error=請輸入1到20個字符的商品名稱
fruits.producing_area.isEmpty=請輸入商品的生產地
- 在Controller方法中捕獲校驗信息
@Controller
@RequestMapping("query")
public class FindControllerTest4 {
private FruitsService fruitsService = new FruitsServiceImpl();
@RequestMapping("queryFruitsByCondition")
public String queryFruitsByCondition(Model model, @Validated Fruits fruits, BindingResult bindingResult){
// 獲取校驗錯誤信息
List<ObjectError> allErrors = null;
if (bindingResult.hasErrors()){
allErrors = bindingResult.getAllErrors();
for(ObjectError objectError:allErrors){
// 輸出錯誤信息
System.out.println(objectError.getDefaultMessage());
}
}
List<Fruits> findList = null;
if(fruits==null || (fruits.getName()==null && fruits.getProducing_area()==null)){
//如果fruits或查詢條件為空,默認查詢所有數據
findList = fruitsService.queryFruitsList();
} else {
// 如果fruits查詢條件不為空损痰,按條件查詢
findList = fruitsService.queryFruitsByCondition(fruits);
}
model.addAttribute("fruitsList", findList);
return "findFruits";
}
}
在Controller方法的形參fruits前面添加了@Validated注解,在后面添加了BindingResult類酒来。一般會在需要校驗的Bean形參前面加@Validated注解卢未,標注該參數需要執(zhí)行Validated校驗,而在需要校驗的Bean形參后面添加BindingResult參數接收校驗的出錯信息。
@Validated和BindingResult注解時成對出現的辽社,并且在形參中出現的順序是固定的(一前一后)伟墙。
- 補充Service層代碼
package cn.com.mvc.service;
import cn.com.mvc.model.Fruits;
import java.util.ArrayList;
import java.util.List;
public class FruitsServiceImpl implements FruitsService {
public List<Fruits> fruitsList = null;
public List<Fruits> init(){
if (fruitsList == null){
fruitsList = new ArrayList<Fruits>();
Fruits apple = new Fruits();
apple.setId(1);
apple.setName("紅富士蘋果");
apple.setPrice(2.3);
apple.setProducing_area("山東");
Fruits banana = new Fruits();
banana.setId(2);
banana.setName("香蕉");
banana.setPrice(1.5);
banana.setProducing_area("上海");
fruitsList.add(apple);
fruitsList.add(banana);
return fruitsList;
}else {
return fruitsList;
}
}
@Override
public List<Fruits> queryFruitsList() {
return init();
}
@Override
public Fruits queryFruitById(Integer id) {
init();
Fruits f;
for(int i = 0; i < fruitsList.size(); i++){
f = fruitsList.get(i);
if (f.getId() == id)
return f;
}
return null;
}
@Override
public List<Fruits> queryFruitsByCondition(Fruits fruits) {
init();
String name = fruits.getName();
String area = fruits.getProducing_area();
List<Fruits> queryList = new ArrayList<Fruits>();
Fruits f;
for (int i = 0; i < fruitsList.size(); i++){
f = fruitsList.get(i);
if ((!name.equals("")&&f.getName().contains(name)) ||
(!area.equals("")&&f.getProducing_area().contains(area))){
queryList.add(f);
}
}
return queryList.size()>0?queryList:null;
}
}
1.3 測試校驗結果
- controller層添加
//將錯誤傳到頁面
model.addAttribute("allErrors", allErrors);
- 前端頁面定義一個div,專門用來顯示錯誤滴铅。
<%--顯示錯誤信息--%>
<c:if test="${allErrors != null}">
<c:forEach items="${allErrors}" var="error">
<font color="red">${error.defaultMessage}</font><br/>
</c:forEach>
</c:if>
2. 分組校驗
當使用Bean Validation校驗框架的時候戳葵,一般都會將校驗信息在對應的實體JavaBean中,上面代碼中有汉匙。
問題:所有使用該實體類的Controller類對應的方法都要進習慣一次校驗拱烁,但有些Controller僅僅將Fruits實體類作為查詢條件(如里面只有一個id),這樣的Fruits實體類再進行Bean Validation校驗時就會出現問題噩翠,導致該方法拋出不該拋的異常戏自。
? SpringMVC提供“分組校驗”,將不同校驗規(guī)則分給不同的組伤锚,當在Controller方法中校驗相關的實體類Bean時擅笔,可以指定不同的組使用不同的校驗規(guī)則。
首先創(chuàng)建兩個組接口屯援,稱為FruitGroup1猛们、FruitGroup2
package cn.com.mvc.validator.group;
//校驗分組1
public interface FruitsGroup1 {
}
package cn.com.mvc.validator.group;
//校驗分組2
public interface FruitsGroup2 {
}
在Fruits實體類中的兩個校驗分配給不同的組:
public class Fruits {
private int id;
@Size(min=0, max=10, message="{fruits.name.length.error}", groups = {FruitsGroup1.class})
private String name;
private double price;
@NotEmpty(message="{fruits.producing_area.isEmpty}", groups = {FruitsGroup2.class})
private String producing_area;//產地
//其余代碼省略
}
之后就在Controller中的@Validation注解中添加一個value值即可,如:
@RequestMapping("queryFruitsByCondition")
public String queryFruitsByCondition(Model model, @Validated(value=FruitsGroup1.class) Fruits fruits, BindingResult bindingResult){
//代碼省略
}
3. Spring Validator接口校驗
Validator接口校驗是SpringMVC自己的校驗機制狞洋。SpringMVC為其提供了接口弯淘,可以用它來驗證自己定義的實體對象。
原理:使用了一個Errors對象工作徘铝,當驗證器驗證失敗的時候耳胎,會向Errors對象填充驗證失敗的信息。
區(qū)別:Bean Validation是在需要校驗的JavaBean中進行約束指定惕它,而Spring的Validator接口校驗是實現Validator接口怕午,并編寫指定類型的校驗規(guī)則。
接口使用:
3.1 定義一個User實體類
package cn.com.mvc.model;
public class User {
private String username;
private String password;
//get和set方法省略
}
3.2 編寫一個Validator接口的實現類淹魄,并實現其supports方法和validate方法郁惜。
supports方法的作用:判斷當前的Validator實現類是否支持校驗當前需要校驗的實體類,如果支持甲锡,該方法返回true兆蕉,此時才可以調用valida方法來對需要校驗的實體類進行校驗。
validate方法的作用:編寫具體的校驗邏輯缤沦,并根據不同的校驗結果虎韵,將錯誤放入錯誤對象Errors中。其中Errors是存儲和暴露數據綁定錯誤和驗證錯誤相關信息的接口缸废,其提供了存儲和獲取錯誤消息的方法包蓝。
編寫一個Validator接口的實現類:
package cn.com.mvc.validator;
import cn.com.mvc.model.User;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
public class UserValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return User.class.equals(aClass);
}
@Override
public void validate(Object o, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "username", "Username.is.empty", "用戶名不能為空");
User user = (User)o;
if (null == user.getPassword() || "".equals(user.getPassword())){
//指定驗證失敗的字段名驶社、錯誤名、默認錯誤信息
errors.rejectValue("password", "Password.is.empty", "密碼不能為空测萎。");
} else if (user.getPassword().length() < 6){
//指定驗證失敗的字段名亡电、錯誤嗎、默認錯誤信息
errors.rejectValue("password","length.too.short","密碼長度不小于6位硅瞧。");
}
}
}
關于Errors的兩個方法份乒,第一個rejectValue方法設置了錯誤字段名為“password”,注冊全局錯誤碼“Password.is.empty”腕唧。第二個rejectValue方法除了設置錯誤字段名和全局錯誤碼外或辖,還設置默認消息“密碼長度不得小于6位”,當校驗器從messageSource沒有找到錯誤碼“Password.is.empty.”對應的錯誤消息時四苇,則顯示默認消息“密碼長度不得小于6位”孝凌。
3.3 在Controller中的initBinder方法中位DataBinder設置一個Validator(即UserValidator),然后在相關方法中添加BindingResult對象月腋。
package cn.com.mvc.controller;
import cn.com.mvc.model.User;
import cn.com.mvc.validator.UserValidator;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.validation.Valid;
import java.util.List;
@Controller
@RequestMapping("user")
public class UserControllerTest {
@InitBinder
public void initBinder(DataBinder binder){
binder.setValidator(new UserValidator());
}
@RequestMapping("toLogin")
public String toLoginPage(){
//跳轉至登錄頁面
return "/user/login";
}
@RequestMapping("login")
public String login(Model model, @Valid User user, BindingResult result){
//登錄檢測
List<ObjectError> allErrors = null;
if(result.hasErrors()){
allErrors = result.getAllErrors();
for (ObjectError objectError : allErrors){
//輸出錯誤信息
System.out.println("code="+objectError.getCode()+" DefaultMessage="+objectError.getDefaultMessage());
//將錯誤信息傳到頁面
model.addAttribute("allErrors",allErrors);
}
return "/user/login";
} else {
//其他的業(yè)務邏輯
}
return "/user/loginSuccess";
}
}
3.4 校驗測試
jsp頁面代碼蟀架,分別為login.jsp和loginSuccess.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>登錄頁面</title>
</head>
<body>
<form action="/user/login.action" method="post">
賬號:<input type="text" name="username"/><br/>
密碼:<input type="password" name="password"/><br/>
<input type="submit" value="Login"/>
<%--錯誤信息展示--%>
<c:if test="${allErrors != null}">
<c:forEach items="${allErrors}" var="error">
<br/><font color="red">${error.defaultMessage}</font>
</c:forEach>
</c:if>
</form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登錄成功頁面</title>
</head>
<body>
<font color="green">登錄成功!</font>
</body>
</html>
下面選取了一張測試結果圖:
注:在UserControllerTest中榆骚,login首先會接收到客戶端發(fā)送的一個User對象(這是因為包裝類型參數綁定)片拍,利用前面定義的UserValidator對接收到的User對象進行校驗。而在login方法的User形參前妓肢,使用@Valid注解對其進行標注捌省,這是因為只有當使用@Valid標注需要校驗的參數時,Spring才會對其進行校驗碉钠。而在校驗的參數后面纲缓,必須給定一個包含Errors的參數,可以是Errors本身喊废,也可以是其子類BindingResult祝高。如果不設置包含Errors的參數,Spring會直接拋出異常污筷,而設置后Spring會將異常的處理權交給開發(fā)人員工闺,有開發(fā)人員來處理形參中包含Error參數的對象。注意瓣蛀,這個參數必須緊挨著@Valid注解標注的參數陆蟆。