Spring Boot JSR303 参数校验
JSR-303 简介
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。
此实现与 Hibernate ORM 没有任何关系。JSR 303 用于对 Java Bean 中的字段的值进行验证。Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。注:可以使用注解的方式进行验证
准备校验时使用的 jar
validation-api-1.0.0.GA.jar:JDK的接口;
hibernate-validator-4.2.0.Final.jar是对上述接口的实现;
log4j、slf4j、slf4j-log4j
JSR 303 基本的校验规则
空检查
@Null 验证对象是否为 null
@NotNull 验证对象是否不为 null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan 检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。
数值检查
建议使用在 Stirng,Integer 类型,不建议使用在 int 类型上,因为表单值为""时无法转换为 int,但可以转换为 Stirng 为"",Integer 为 null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 被指定的元素必须在合适的范围内, @Range(min=10000,max=50000,message="range.bean.wage")
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber 信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
代码实现
实体类
import org.hibernate.validator.constraints.Length;import org.hibernate.validator.constraints.NotEmpty;/*** 用户数据模板*/public class User {@NotEmpty(message = "姓名不能为空")private String name;private Integer age;@NotEmpty(message = "密码不能为空")@Length(min = 6,max = 20,message = "密码长度应该大于6位小于20位")private String password;public User() {}public User(String name, Integer age, String password) {this.name = name;this.age = age;this.password = password;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", password='" + password + '\'' +'}';}}
Controller
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.validation.BindingResult;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletResponse;import javax.validation.Valid;/*** 用户控制层** @author LIUTAO* @version 2017/3/29* @see*/@RestController@RequestMapping("/test")public class UserController {private Logger logger = LoggerFactory.getLogger(UserController.class);@PostMapping(value = "/user")public@ResponseBodyString postUser(@Valid @RequestBody User user, BindingResult result, HttpServletResponse response) {if(result.hasErrors()){List<ObjectError> list = result.getAllErrors();StringBuilder stringBuilder = new StringBuilder();for(ObjectError error:list){stringBuilder.append("\n"+error.getDefaultMessage());}response.setStatus(HttpStatus.BAD_REQUEST.value());return stringBuilder.toString();}return "ok";}}
自定义验证标签
针对某些需求,现有的标签无法满足我们的需要的时候,就需要我们定义自己的标签。实例如下:
定义标签
import javax.validation.Constraint;import javax.validation.Payload;import java.lang.annotation.*;import static java.lang.annotation.ElementType.*;import static java.lang.annotation.RetentionPolicy.RUNTIME;/*** 演示自定义参数校验注解* 校验list集合中是否有null元素*/@Target({ANNOTATION_TYPE, METHOD, ElementType.FIELD})@Retention(RUNTIME)@Documented@Constraint(validatedBy = ListNotHasNullValidatorImpl.class)//此处指定了注解的实现类为ListNotHasNullValidatorImplpublic @interface ListNotHasNull {/*** 添加value属性,可以作为校验时的条件,若不需要,可去掉此处定义*/int value() default 0;String message() default "List集合中不能含有null元素";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};/*** 定义List,为了让Bean的一个属性上可以添加多套规则*/@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})@Retention(RUNTIME)@Documented@interface List {ListNotHasNull[] value();}}
标签验证实现类
import org.springframework.stereotype.Service;import javax.validation.ConstraintValidator;import javax.validation.ConstraintValidatorContext;import java.util.List;/*** 演示实现ListNotHasNull校验注解的实现类*/@Servicepublic class ListNotHasNullValidatorImpl implements ConstraintValidator<ListNotHasNull, List> {private int value;@Overridepublic void initialize(ListNotHasNull constraintAnnotation) {//传入value 值,可以在校验中使用this.value = constraintAnnotation.value();}public boolean isValid(List list, ConstraintValidatorContext constraintValidatorContext) {for (Object object : list) {if (object == null) {//如果List集合中含有Null元素,校验失败return false;}}return true;}}
所需的数据模型
/*** 员工数据模型*/public class Employee {private String name;private String age;private String cellPhone;public Employee() {}public Employee(String name, String age, String cellPhone) {this.name = name;this.age = age;this.cellPhone = cellPhone;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}public String getCellPhone() {return cellPhone;}public void setCellPhone(String cellPhone) {this.cellPhone = cellPhone;}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\'' +", age='" + age + '\'' +", cellPhone='" + cellPhone + '\'' +'}';}}
import org.hibernate.validator.constraints.Length;import org.hibernate.validator.constraints.NotEmpty;import javax.validation.Valid;import java.util.List;/*** 公司数据模型*/public class Company {@NotEmpty(message = "公司名字不能为空")private String name;@Length(min = 2,max = 20,message = "地址信息必须在2到20个字符之间")private String address;@NotEmpty(message = "员工信息不能为空")@ListNotHasNull@Validprivate List<Employee> employees;public Company() {}public Company(String name, String address, List<Employee> employees) {this.name = name;this.address = address;this.employees = employees;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public List<Employee> getEmployees() {return employees;}public void setEmployees(List<Employee> employees) {this.employees = employees;}@Overridepublic String toString() {return "Company{" +"name='" + name + '\'' +", address='" + address + '\'' +", employees=" + employees +'}';}}
从上面的 Company 我们可以看见已经使用了自定义的 @ListNotHasNull 标签
Controller
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.validation.BindingResult;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.RestController;import javax.servlet.http.HttpServletResponse;import javax.validation.Valid;/*** 公司Controller*/@RestController@RequestMapping("/test")public class CompanyController {private Logger logger = LoggerFactory.getLogger(UserController.class);@PostMapping(value = "/company")public String postCompany(@Valid @RequestBody Company company, BindingResult result,HttpServletResponse response){if(result.hasErrors()){List<ObjectError> list = result.getAllErrors();StringBuilder stringBuilder = new StringBuilder();for(ObjectError error:list){stringBuilder.append("\n"+error.getDefaultMessage());}response.setStatus(HttpStatus.BAD_REQUEST.value());return stringBuilder.toString();}return "ok";}}
分组校验
当我们针对同一个model的校验不一样的时候该怎么办?比如针对同一个model的添加和修改的时候?这个时候我们就需要采用分组验证。
定义分别代表修改和添加校验规则的接口
/*** person模型新增时的参数校验规则*/public interface PersonAddView {}
/*** person模型修改时的参数校验规则*/public interface PersonUpdateView {}
在数据模型上添加相应的校验规则
import org.hibernate.validator.constraints.Length;import javax.validation.constraints.Max;import javax.validation.constraints.Min;import javax.validation.constraints.NotNull;/*** person数据模型*/public class Person {private Long id;/*** 针对验证标签里面添加groups说明仅仅在特定的验证规则里面起作用,如果不加,那么就在默认的验证规则里面起作用。*/@NotNull(groups = {PersonAddView.class},message = "添加的姓名不能为空")@Length(min = 2,max = 10,groups = {PersonUpdateView.class},message = "修改时的姓名必须在2到10个字符之间")private String name;@NotNull(groups = {PersonAddView.class}, message = "添加用户时地址不能为空")private String address;@Min(value = 18, groups = {PersonAddView.class}, message = "年龄不能低于18岁")@Max(value = 30, groups = {PersonUpdateView.class}, message = "年龄不能超过30岁")private Integer age;public Person() {}public Person(Long id, String name, String address, Integer age) {this.id = id;this.name = name;this.address = address;this.age = age;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +", address='" + address + '\'' +", age=" + age +'}';}}
给参数添加校验
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.validation.BindingResult;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletResponse;import javax.validation.groups.Default;/*** 人员Controller*/@RestController@RequestMapping("/test")public class PersonController {private Logger logger = LoggerFactory.getLogger(PersonController.class);/*** 处@Validated(PersonAddView.class) 表示使用PersonAndView这套校验规则,若使用@Valid 则表示使用默认校验规则,* 若两个规则同时加上去,则只有第一套起作用** @param person*/@PostMapping(value = "/person")public String addPerson(@RequestBody @Validated({PersonAddView.class, Default.class}) Person person, BindingResult result,HttpServletResponse response) {logger.debug("enter post person");logger.debug("the information of person :"+ person);return ValidateUtility.judgeValidate(result,response);}/*** 修改Person对象* 此处启用PersonModifyView 这个验证规则* @param person*/@PutMapping(value = "/person")public String modifyPerson(@RequestBody @Validated(value = {PersonUpdateView.class}) Person person, BindingResult result,HttpServletResponse response) {logger.debug("enter put person");logger.debug("the information of person :"+ person);if(result.hasErrors()){List<ObjectError> list = result.getAllErrors();StringBuilder stringBuilder = new StringBuilder();for(ObjectError error:list){stringBuilder.append("\n"+error.getDefaultMessage());}response.setStatus(HttpStatus.BAD_REQUEST.value());return stringBuilder.toString();}return "ok";}}
Controller 方法中的非对象参数验证
如果我们传入的参数没必要进行封装成对象,那么这个时候对参数的校验就得使用参数验证了。
添加参数验证配置类
import org.springframework.context.annotation.Bean;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.ResponseStatus;import javax.validation.ValidationException;/*** 参数验证配置类*/@ControllerAdvice@Componentpublic class GlobalExceptionHandler {@Beanpublic MethodValidationPostProcessor methodValidationPostProcessor() {return new MethodValidationPostProcessor();}@ExceptionHandler@ResponseBody@ResponseStatus(HttpStatus.BAD_REQUEST)public String handle(ValidationException exception) {return "传入参数不符合要求";}}
在 Controller 上添加参数验证
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.HttpStatus;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.*;import javax.validation.constraints.Min;/*** 员工Controller*/@RestController@RequestMapping("/test")@Validatedpublic class EmployeeController {private Logger logger = LoggerFactory.getLogger(EmployeeController.class);@GetMapping("/employee")@ResponseStatus(HttpStatus.OK)public @ResponseBodyString check(@RequestParam @Min(value = 10,message = "名字长度必须大于10") int age) {logger.debug("enter employee get");return "ok";}}
统一异常处理
之前我们演示的都是在 Controller 中用 BindingResult 取校验的错误信息,在 Spring Boot 中默认的操作中,会向客户端返回一个 400 的状态码,并产生了一个 BindException 异常,我们可以拦截这个异常,这样就可以不用在每个需要检验参数的方法中取错误信息了。
import com.yeskery.util.Result;import org.springframework.validation.BindException;import org.springframework.validation.ObjectError;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import java.util.List;/*** @author yeskery* @date 2018/06/02*/@ControllerAdvice@ResponseBodypublic class GlobalExceptionHandler {@ExceptionHandler(value = BindException.class)public Result bindExceptionHandler(BindException e) {List<ObjectError> errors = e.getAllErrors();return Result.noData(errors.get(0).getDefaultMessage());}}
本文部分内容来自:
评论