Spring MVC代码实例系列-06:Spring MVC配置Hibernate-Validator以及自定义校验注解

超级通道 :Spring MVC代码实例系列-绪论

本章主要记录,如何在Spring MVC中添加Hibernate-Validator以及自定义校验注解。本章主要涉及的技术点有:

  1. javax.validation : JSR 303: Bean Validation的一种实现
  2. hibernate.validator : JSR 303: Bean Validation的一种实现
  3. groups : 分组校验
  4. @GroupSequence : 排序分组校验
  5. message.properties : 校验提示信息的配置文件
  6. 自定义注解校验

##1.目录结构

src
\---main
	\---java
	|	\---pers
	|		\---hanchao
	|			\---hespringmvc
	|			 	\---validation
	|			 		\---InsertGroup.java
	|			 		\---UpdateGroup.java
	|			 		\---Student.java
	|			 		\---JsonResult.java
	|			 		\---StudentManageController.java
	\---webapp
		\---message.properties
	\---webapp
		\---validation
		|	\---student.jsp
		\---WEB-INF
		|	\---spring-mvc-servlet.xml
		|	\---web.xml
		\---index.jsp

##2.pom.xml引入相关jar包

    <hibernate-validator.version>5.4.1.Final</hibernate-validator.version>

    <!-- 验证的jar -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>${hibernate-validator.version}</version>
    </dependency>

##3.spring-mvc-servlet.xml配置校验驱动

    <!--注册校验驱动-->
    <mvc:annotation-driven validator="validator"/>
    <!--校验工厂-->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>
    <!--validation message 校验消息-->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="message"/>
        <property name="defaultEncoding" value="utf-8"/>
        <property name="useCodeAsDefaultMessage" value="false"/>
        <property name="cacheSeconds" value="60"/>
    </bean>

##4.Student.java设置校验规则
先看两个分组接口

package pers.hanchao.hespringmvc.validation;

public interface InsertGroup {}
package pers.hanchao.hespringmvc.validation;

public interface UpdateGroup {}

再看被校验的实体类:

package pers.hanchao.hespringmvc.validation;

import org.hibernate.validator.constraints.CreditCardNumber;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;
import pers.hanchao.hespringmvc.validation.custom.annotation.CustomLength;

import javax.validation.GroupSequence;
import javax.validation.constraints.*;
import java.util.Date;
/**
 * <p>javax.validation校验、hibernate.validator校验、分组校验、@GroupSequence分组顺序校验</p>
 * @author hanchao 2018/1/20 14:58
 **/
@GroupSequence({InsertGroup.class,UpdateGroup.class,Student.class})
public class Student {
    /javax.validation/
    //添加学生时,id必为空;修改学生时,id必须有值
    @Null(groups = InsertGroup.class,message = "{Null.student.id}")
    @NotNull(groups = UpdateGroup.class,message = "{NotNull.student.id}")
    private String id;

    @Size(min = 2,max = 16,message = "{Size.student.name}",groups = {InsertGroup.class,UpdateGroup.class})
    private String name;//名字

    @AssertTrue(groups = InsertGroup.class, message = "{AssertTrue.student.newRegister}")
    @AssertFalse(groups = UpdateGroup.class, message = "{AssertTrue.student.newRegister}")
    private boolean newRegister;//是否新注册

    @Max(value = 100, message = "{Max.student.score}",groups = {InsertGroup.class,UpdateGroup.class})
    @Min(value = 0, message = "{Min.student.score}",groups = {InsertGroup.class,UpdateGroup.class})
    private int score;//分数[0-100]

    @DecimalMax(value = "30",inclusive = true, message = "{student.age}",groups = {InsertGroup.class,UpdateGroup.class})
    @DecimalMin(value = "19",inclusive = false, message = "{student.age}",groups = {InsertGroup.class,UpdateGroup.class})
    private String age;//年龄范围[20-30]

    @Digits(integer = 3,fraction = 2, message = "{Digits.student.weight}",groups = {InsertGroup.class,UpdateGroup.class})
    private float weight;//体重格式[xxx.yy]

    @Past(message = "{Past.student.entrance}",groups = {InsertGroup.class,UpdateGroup.class})
    private Date entrance;//入学时间

    @Future(message = "{Future.student.graduation}",groups = {InsertGroup.class,UpdateGroup.class})
    private Date graduation;//毕业时间

    @Pattern(regexp = "^S2018[0-9]{4}$",flags = Pattern.Flag.CASE_INSENSITIVE, message = "{Pattern.student.number}",groups = {InsertGroup.class,UpdateGroup.class})
    private String number;//学号形式 S20180000-S20189999 大小写敏感

    /hibernate.validator/

    @URL(message = "{URL.student.blog}",groups = {InsertGroup.class,UpdateGroup.class})
    private String blog;//个人学生主页

    @Length(min = 1000,max = 5000 ,message = "{Length.student.bonus}",groups = {InsertGroup.class,UpdateGroup.class})
    private String tuition;//学费

    @Range(min = 2000L,max = 4000L,message = "{Rang.student.bonus}",groups = {InsertGroup.class,UpdateGroup.class})
    private String bonus;//奖金

    @CreditCardNumber(message = "{CreditCardNumber}",groups = {InsertGroup.class,UpdateGroup.class})
    private String creditCard;//银行账号
	
	//toString()
	//setter and getter
}

校验规则说明:

  1. @URL@Length、@Range、 @CreditCardNumber属于hibernate.validator,其他的都属于javax.validation

  2. groups = InsertGroup.class表名此字段只在InsertGroup分组被校验。

  3. groups = {InsertGroup.class,UpdateGroup.class}表面此字段在InsertGroupUpdateGroup分组都被校验。

  4. 一个字段可以被多种校验规则注解,如

     @Max(value = 100, message = "{Max.student.score}",groups = {InsertGroup.class,UpdateGroup.class})
     @Min(value = 0, message = "{Min.student.score}",groups = {InsertGroup.class,UpdateGroup.class})
     private int score;//分数[0-100]
    
  5. @GroupSequence({InsertGroup.class,UpdateGroup.class,Student.class})表明了InsertGroupUpdateGroup分组的校验顺序,即:如果InsertGroup分组的校验有错误,则不再进行UpdateGroup分组的校验,直接返回。

  6. @GroupSequence({InsertGroup.class,UpdateGroup.class,Student.class})一定要注意不要忘了把当前实体类Student.class写入其中。

  7. message = "{Length.student.bonus}""是通过message.properties读取校验提示信息的方式。

  8. 多种校验规则可以共用同样的校验提示信息,如:

         @DecimalMax(value = "30",inclusive = true, message = "{student.age}",groups = {InsertGroup.class,UpdateGroup.class})
         @DecimalMin(value = "19",inclusive = false, message = "{student.age}",groups = {InsertGroup.class,UpdateGroup.class})
         private String age;//年龄范围[20-30]
    

##5.message.properties设置校验提示信息

Null.student.id = 学生id必须为空!
NotNull.student.id = 学生id必须不为空!
Size.student.name = 姓名应该在2至16个汉字之间!
CustomLength.student.name = 姓名应该在2至16个字符之间!
AssertTrue.student.newRegister = 必须是新注册的学生!
AssertFalse.student.newRegister = 必须是已经注册的学生!
Max.student.score = 分数必须小于等于100!
Min.student.score = 分数必须大于等于0!
student.age = 年龄必须处于20~30之间!
Digits.student.weight = 体重必须是xxx.yy的格式,如101.52!
Past.student.entrance = 入学时间必须早于今天!
Future.student.graduation = 毕业时间必须晚于今天!
Pattern.student.number = 学号格式必须位于S20180000-S20189999之间!
URL.student.blog = 学生个人主页必须错误!
Length.student.bonus = 学费必须在1000-5000之间!
Rang.student.bonus = 奖金必须在2000-4000之间!
CreditCardNumber = 银行账号不合法!

校验提示信息说明:

  1. 这里的key,例如Null.student.id等,应该与Student.java中的message = "{Null.student.id}"相匹配。

##6.StudentManageController.java控制类对实体进行校验

package pers.hanchao.hespringmvc.validation;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

/**
 * <p>简单的bean validation和hibernate validator的例子</p>
 * @author hanchao 2018/1/20 14:47
 **/
@Controller
@RequestMapping("validation")
public class StudentManageController {

    /**
     * <p>注册学生信息</p>
     * @author hanchao 2018/1/20 14:47
     **/
    @PostMapping("/insert")
    @ResponseBody
    public JsonResult insert(@Validated(InsertGroup.class) @RequestBody Student student, BindingResult bindingResult){
        JsonResult jsonResult = new JsonResult();
        validate(bindingResult, jsonResult);
        System.out.println(jsonResult.toString());
        return jsonResult;
    }

    /**
     * <p>修改学生信息</p>
     * @author hanchao 2018/1/20 14:47
     **/
    @PostMapping("/update")
    @ResponseBody
    public JsonResult update(@Validated(UpdateGroup.class) @RequestBody Student student, BindingResult bindingResult){
        JsonResult jsonResult = new JsonResult();
        validate(bindingResult, jsonResult);
        System.out.println(jsonResult.toString());
        return jsonResult;
    }

    /**
     * <p>对bindingResult进行校验</p>
     * @author hanchao 2018/1/20 14:46
     **/
    private void validate(BindingResult bindingResult, JsonResult jsonResult) {
        if (bindingResult.hasErrors()){
            StringBuffer errors = new StringBuffer();
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            for (ObjectError objectError : allErrors){
                errors.append(objectError.getDefaultMessage() + "<br/>");
            }
            jsonResult.setCodeAndMessage("0",errors.toString());
        }
    }
}

说明:

  1. @Validated(InsertGroup.class) @RequestBody Student student表名只对页面信息进行InsertGroup.class分组相关的校验。
  2. 如果不指定校验哪个分组,如@Validated @RequestBody Student student则会校验所有的字段。
  3. 如果不指定校验哪个分组,如@Validated @RequestBody Student student可以用@Valid @RequestBody Student student替代。

##7.student.jsp页面

<%--
  Created by IntelliJ IDEA.
  User: hanchao
  Date: 2018/1/17
  Time: 21:14
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>学生信息管理页面</title>
    <script type="text/javascript" src="../static/jquery-3.2.1.min.js"></script>
    <style>
        table, td, th{border:1px solid green;background-color: lemonchiffon;text-align:center}
    </style>
</head>
<body>
<p id="errorString"/>
    <form>
        <table>
            <tr>
                <td><label>姓名</label></td>
                <td><input name="name" type="text"></td>
            </tr>
            <tr>
                <td><label>是否新注册用户</label></td>
                <td><input name="newRegister" type="text"></td>
            </tr>
            <tr>
                <td><label>学分</label></td>
                <td><input name="score" type="text"></td>
            </tr>
            <tr>
                <td><label>年龄</label></td>
                <td><input name="age" type="text"></td>
            </tr>
            <tr>
                <td><label>姓名</label></td>
                <td><input name="name" type="text"></td>
            </tr>
            <tr>
                <td><label>体重(kg)</label></td>
                <td><input name="weight" type="text"></td>
            </tr>
            <tr>
                <td><label>入学时间</label></td>
                <td><input name="entrance" type="text"></td>
            </tr>
            <tr>
                <td><label>毕业时间</label></td>
                <td><input name="graduation" type="text"></td>
            </tr>
            <tr>
                <td><label>学号</label></td>
                <td><input name="number" type="text"></td>
            </tr>
            <tr>
                <td><label>个人学生主页</label></td>
                <td><input name="blog" type="text"></td>
            </tr>
            <tr>
                <td><label>学费</label></td>
                <td><input name="tuition" type="text"></td>
            </tr>
            <tr>
                <td><label>奖金</label></td>
                <td><input name="bonus" type="text"></td>
            </tr>
            <tr>
                <td><label>银行卡号</label></td>
                <td><input name="creditCard" type="text"></td>
            </tr>

            <tr>
                <td><input type="button" value="注册" οnclick="insert()"/></td>
                <td><input type="button" value="查询" οnclick="update()"/></td>
            </tr>
        </table>
    </form>
</body>
<script type="text/javascript">
    //插入一条学生信息
    function insert() {
        $.ajax({
            type:"POST",
            url:"/validation/insert",
            data:JSON.stringify({
                name:$('input[name="name"]').val(),
                newRegister:$('input[name="newRegister"]').val(),
                score:$('input[name="score"]').val(),
                age:$('input[name="age"]').val(),
                weight:$('input[name="weight"]').val(),
                entrance:$('input[name="entrance"]').val(),
                graduation:$('input[name="graduation"]').val(),
                number:$('input[name="number"]').val(),
                reward:$('input[name="reward"]').val(),
                opinion:$('input[name="opinion"]').val(),
                email:$('input[name="email"]').val(),
                blog:$('input[name="blog"]').val(),
                tuition:$('input[name="tuition"]').val(),
                bonus:$('input[name="bonus"]').val(),
                creditCard:$('input[name="creditCard"]').val()
            }),
            contentType:'application/json;charset=utf-8',
            dataType:'json',
            success:function (jsonResult) {
                console.log(jsonResult);
                $("#errorString").html(jsonResult.message);
            }
        })
    }
    //更新学生信息
    function update() {
        $.ajax({
            type:"POST",
            url:"/validation/update",
            data:JSON.stringify({
                name:$('input[name="name"]').val(),
                newRegister:$('input[name="newRegister"]').val(),
                score:$('input[name="score"]').val(),
                age:$('input[name="age"]').val(),
                weight:$('input[name="weight"]').val(),
                entrance:$('input[name="entrance"]').val(),
                graduation:$('input[name="graduation"]').val(),
                number:$('input[name="number"]').val(),
                reward:$('input[name="reward"]').val(),
                opinion:$('input[name="opinion"]').val(),
                email:$('input[name="email"]').val(),
                blog:$('input[name="blog"]').val(),
                tuition:$('input[name="tuition"]').val(),
                bonus:$('input[name="bonus"]').val(),
                creditCard:$('input[name="creditCard"]').val()
            }),
            contentType:'application/json;charset=utf-8',
            dataType:'json',
            success:function (jsonResult) {
                console.log(jsonResult);
                $("#errorString").html(jsonResult.message);
            }
        })
    }
</script>
</html>

###8.result

这里写图片描述

##9.自定义注解校验
场景(假设情况…):

  • @Length校验的是输入字符的个数,例如你好的长度是2
  • 如果name字段的校验规则为(min = 2,max = 4),在mysql中name字段的长度设计为varchar(4)
  • 这种校验应用在mysql 5.0以后是没问题的,因为Mysql 5.0以后varchar存储的就是字符数,name字段最多可以存储4个汉字。
  • 但是这种校验应用在Mysql 4.0数据库中是有问题的,因为Mysql 4.0存储的是字节数,name组多存储1个UTF-8汉字或者2个GBG汉字。
  • 如果前台传过来的name=张三丰,虽然校验不报错,但是插入mysql时,肯定会报错。

这里引入了自定义校验注解的概念,需要设计一个新的注解实现以下功能:

  1. 能够通过minmax设置最小和最大长度。
  2. 校验的是输入内容的字节数。
  3. 能够设置编码格式。

为了完成这个自定义注解,下面分两步进行:
目录结构(续接之前的目录结构):

\---validation
	\---custom
		\---annotation
			\---CustomLength.java
		\---validator
			\---CustomLengthValidator.java

###9.1.CustomLength.java自定义校验注解

package pers.hanchao.hespringmvc.validation.custom.annotation;

import pers.hanchao.hespringmvc.validation.custom.validator.CustomLengthValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * <p>校验字符串长度,中文按照charset进行计算</p>
 * @author hanchao 2018/1/20 15:40
 **/
@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CustomLengthValidator.class)
@Documented
public @interface CustomLength {
	
	long min() default 0;
	long max() default Integer.MAX_VALUE; 
	String charset() default "gbk";
	
	String message() default "length must be between {min} and {max}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

###9.2.CustomLengthValidator自定义校验规则类

package pers.hanchao.hespringmvc.validation.custom.validator;

import java.io.UnsupportedEncodingException;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import pers.hanchao.hespringmvc.validation.custom.annotation.CustomLength;

/**
 * <p>自定义校验规则诶</p>
 * @author hanchao 2018/1/20 17:04
 **/
public class CustomLengthValidator implements ConstraintValidator<CustomLength, String> {

	private static final Log log = LoggerFactory.make();
	private long min;
	private long max;
	private String charset;
	
	@Override
	public void initialize(CustomLength parameters) {
		//do nothing
		this.min = parameters.min();
		this.max = parameters.max();
		this.charset = parameters.charset();
		validateParameters();
	}
	@Override
	public boolean isValid(String value, ConstraintValidatorContext context) {
		if(null == value){
			value = "";
		}
		long length = 0;
		try {
			length = ((String)value).getBytes(charset).length;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		boolean result = (length >= min) && (length <= max);
		log.info("CustomLength.validator:[value:" + (String)value + ",min:" + min + ",max:" + max + ",length:" + length + "],result:" + result);
		return (length >= min) && (length <= max);
	}
	private void validateParameters() {
	    if (this.min < 0) {
	      throw log.getMinCannotBeNegativeException();
	    }
	    if (this.max < 0) {
	      throw log.getMaxCannotBeNegativeException();
	    }
	    if (this.max < this.min)
	      throw log.getLengthCannotBeNegativeException();
	}
}

以上,自定义校验注解CustomLenght定义完毕,下面进行实践。
###9.3.修改Student.java
为了方便演示,注释掉了其他校验

//    @Size(min = 2,max = 4,message = "{Size.student.name}",groups = {InsertGroup.class,UpdateGroup.class})
@CustomLength(min = 2,max = 4,charset = "utf-8",message = "{CustomLength.student.name}",groups = {InsertGroup.class,UpdateGroup.class})
private String name;//名字

##9.4.修改message.properties
添加提示信息

CustomLength.student.name = 姓名应该在2至4个字节之间!

###9.5.result

这里写图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值