SpringMVC[5]--Validation校驗

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>

下面選取了一張測試結果圖:


測試結果.png

注:在UserControllerTest中榆骚,login首先會接收到客戶端發(fā)送的一個User對象(這是因為包裝類型參數綁定)片拍,利用前面定義的UserValidator對接收到的User對象進行校驗。而在login方法的User形參前妓肢,使用@Valid注解對其進行標注捌省,這是因為只有當使用@Valid標注需要校驗的參數時,Spring才會對其進行校驗碉钠。而在校驗的參數后面纲缓,必須給定一個包含Errors的參數,可以是Errors本身喊废,也可以是其子類BindingResult祝高。如果不設置包含Errors的參數,Spring會直接拋出異常污筷,而設置后Spring會將異常的處理權交給開發(fā)人員工闺,有開發(fā)人員來處理形參中包含Error參數的對象。注意瓣蛀,這個參數必須緊挨著@Valid注解標注的參數陆蟆。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市惋增,隨后出現的幾起案子叠殷,更是在濱河造成了極大的恐慌,老刑警劉巖诈皿,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溪猿,死亡現場離奇詭異钩杰,居然都是意外死亡纫塌,警方通過查閱死者的電腦和手機诊县,發(fā)現死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來措左,“玉大人依痊,你說我怎么就攤上這事≡跖” “怎么了胸嘁?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凉逛。 經常有香客問我性宏,道長,這世上最難降的妖魔是什么状飞? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任毫胜,我火速辦了婚禮,結果婚禮上诬辈,老公的妹妹穿的比我還像新娘酵使。我一直安慰自己,他們只是感情好焙糟,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布口渔。 她就那樣靜靜地躺著,像睡著了一般穿撮。 火紅的嫁衣襯著肌膚如雪缺脉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天悦穿,我揣著相機與錄音攻礼,去河邊找鬼。 笑死咧党,一個胖子當著我的面吹牛秘蛔,可吹牛的內容都是我干的。 我是一名探鬼主播傍衡,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼深员,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛙埂?” 一聲冷哼從身側響起倦畅,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绣的,沒想到半個月后叠赐,有當地人在樹林里發(fā)現了一具尸體欲账,經...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年芭概,在試婚紗的時候發(fā)現自己被綠了赛不。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡罢洲,死狀恐怖踢故,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情惹苗,我是刑警寧澤殿较,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站桩蓉,受9級特大地震影響淋纲,放射性物質發(fā)生泄漏。R本人自食惡果不足惜院究,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一洽瞬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧儡首,春花似錦片任、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至氛濒,卻和暖如春产场,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舞竿。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工京景, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骗奖。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓确徙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親执桌。 傳聞我的和親對象是個殘疾皇子鄙皇,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容