超级通道 :Spring MVC代码实例系列-绪论
本章主要记录,如何在Spring MVC中添加Hibernate-Validator以及自定义校验注解。本章主要涉及的技术点有:
##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.Finalhibernate-validator.version>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validatorartifactId>
<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>
<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;
/**
* javax.validation校验、hibernate.validator校验、分组校验、@GroupSequence分组顺序校验
* @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
}
校验规则说明:
@URL
、@Length
、@Range、 @CreditCardNumber
属于hibernate.validator
,其他的都属于javax.validation
。
groups = InsertGroup.class
表名此字段只在InsertGroup
分组被校验。
groups = {InsertGroup.class,UpdateGroup.class}
表面此字段在InsertGroup
和UpdateGroup
分组都被校验。
一个字段可以被多种校验规则注解,如
@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]
@GroupSequence({InsertGroup.class,UpdateGroup.class,Student.class})
表明了InsertGroup
和UpdateGroup
分组的校验顺序,即:如果InsertGroup
分组的校验有错误,则不再进行UpdateGroup
分组的校验,直接返回。
@GroupSequence({InsertGroup.class,UpdateGroup.class,Student.class})
一定要注意不要忘了把当前实体类Student.class
写入其中。
message = "{Length.student.bonus}""
是通过message.properties
读取校验提示信息的方式。
多种校验规则可以共用同样的校验提示信息,如:
@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 = 银行账号不合法!
校验提示信息说明:
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;
/**
* 简单的bean validation和hibernate validator的例子
* @author hanchao 2018/1/20 14:47
**/
@Controller
@RequestMapping("validation")
public class StudentManageController {
/**
* 注册学生信息
* @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;
}
/**
* 修改学生信息
* @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;
}
/**
* 对bindingResult进行校验
* @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() + "
");
}
jsonResult.setCodeAndMessage("0",errors.toString());
}
}
}
说明:
@Validated(InsertGroup.class) @RequestBody Student student
表名只对页面信息进行InsertGroup.class
分组相关的校验。@Validated @RequestBody Student student
则会校验所有的字段。@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" %>
学生信息管理页面
###8.result
##9.自定义注解校验
场景(假设情况…):
@Length
校验的是输入字符的个数,例如你好
的长度是2name
字段的校验规则为(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
时,肯定会报错。这里引入了自定义校验注解的概念,需要设计一个新的注解实现以下功能:
min
和max
设置最小和最大长度。为了完成这个自定义注解,下面分两步进行:
目录结构(续接之前的目录结构):
\---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;
/**
* 校验字符串长度,中文按照charset进行计算
* @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;
/**
* 自定义校验规则诶
* @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