yeskery

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=)

代码实现

实体类

  1. import org.hibernate.validator.constraints.Length;
  2. import org.hibernate.validator.constraints.NotEmpty;
  3. /**
  4. * 用户数据模板
  5. */
  6. public class User {
  7. @NotEmpty(message = "姓名不能为空")
  8. private String name;
  9. private Integer age;
  10. @NotEmpty(message = "密码不能为空")
  11. @Length(min = 6,max = 20,message = "密码长度应该大于6位小于20位")
  12. private String password;
  13. public User() {
  14. }
  15. public User(String name, Integer age, String password) {
  16. this.name = name;
  17. this.age = age;
  18. this.password = password;
  19. }
  20. public String getName() {
  21. return name;
  22. }
  23. public void setName(String name) {
  24. this.name = name;
  25. }
  26. public Integer getAge() {
  27. return age;
  28. }
  29. public void setAge(Integer age) {
  30. this.age = age;
  31. }
  32. public String getPassword() {
  33. return password;
  34. }
  35. public void setPassword(String password) {
  36. this.password = password;
  37. }
  38. @Override
  39. public String toString() {
  40. return "User{" +
  41. "name='" + name + '\'' +
  42. ", age=" + age +
  43. ", password='" + password + '\'' +
  44. '}';
  45. }
  46. }

Controller

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.validation.BindingResult;
  4. import org.springframework.web.bind.annotation.*;
  5. import javax.servlet.http.HttpServlet;
  6. import javax.servlet.http.HttpServletResponse;
  7. import javax.validation.Valid;
  8. /**
  9. * 用户控制层
  10. *
  11. * @author LIUTAO
  12. * @version 2017/3/29
  13. * @see
  14. */
  15. @RestController
  16. @RequestMapping("/test")
  17. public class UserController {
  18. private Logger logger = LoggerFactory.getLogger(UserController.class);
  19. @PostMapping(value = "/user")
  20. public
  21. @ResponseBody
  22. String postUser(@Valid @RequestBody User user, BindingResult result, HttpServletResponse response) {
  23. if(result.hasErrors()){
  24. List<ObjectError> list = result.getAllErrors();
  25. StringBuilder stringBuilder = new StringBuilder();
  26. for(ObjectError error:list){
  27. stringBuilder.append("\n"+error.getDefaultMessage());
  28. }
  29. response.setStatus(HttpStatus.BAD_REQUEST.value());
  30. return stringBuilder.toString();
  31. }
  32. return "ok";
  33. }
  34. }

自定义验证标签

针对某些需求,现有的标签无法满足我们的需要的时候,就需要我们定义自己的标签。实例如下:

定义标签

  1. import javax.validation.Constraint;
  2. import javax.validation.Payload;
  3. import java.lang.annotation.*;
  4. import static java.lang.annotation.ElementType.*;
  5. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  6. /**
  7. * 演示自定义参数校验注解
  8. * 校验list集合中是否有null元素
  9. */
  10. @Target({ANNOTATION_TYPE, METHOD, ElementType.FIELD})
  11. @Retention(RUNTIME)
  12. @Documented
  13. @Constraint(validatedBy = ListNotHasNullValidatorImpl.class)//此处指定了注解的实现类为ListNotHasNullValidatorImpl
  14. public @interface ListNotHasNull {
  15. /**
  16. * 添加value属性,可以作为校验时的条件,若不需要,可去掉此处定义
  17. */
  18. int value() default 0;
  19. String message() default "List集合中不能含有null元素";
  20. Class<?>[] groups() default {};
  21. Class<? extends Payload>[] payload() default {};
  22. /**
  23. * 定义List,为了让Bean的一个属性上可以添加多套规则
  24. */
  25. @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
  26. @Retention(RUNTIME)
  27. @Documented
  28. @interface List {
  29. ListNotHasNull[] value();
  30. }
  31. }

标签验证实现类

  1. import org.springframework.stereotype.Service;
  2. import javax.validation.ConstraintValidator;
  3. import javax.validation.ConstraintValidatorContext;
  4. import java.util.List;
  5. /**
  6. * 演示实现ListNotHasNull校验注解的实现类
  7. */
  8. @Service
  9. public class ListNotHasNullValidatorImpl implements ConstraintValidator<ListNotHasNull, List> {
  10. private int value;
  11. @Override
  12. public void initialize(ListNotHasNull constraintAnnotation) {
  13. //传入value 值,可以在校验中使用
  14. this.value = constraintAnnotation.value();
  15. }
  16. public boolean isValid(List list, ConstraintValidatorContext constraintValidatorContext) {
  17. for (Object object : list) {
  18. if (object == null) {
  19. //如果List集合中含有Null元素,校验失败
  20. return false;
  21. }
  22. }
  23. return true;
  24. }
  25. }

所需的数据模型

  1. /**
  2. * 员工数据模型
  3. */
  4. public class Employee {
  5. private String name;
  6. private String age;
  7. private String cellPhone;
  8. public Employee() {
  9. }
  10. public Employee(String name, String age, String cellPhone) {
  11. this.name = name;
  12. this.age = age;
  13. this.cellPhone = cellPhone;
  14. }
  15. public String getName() {
  16. return name;
  17. }
  18. public void setName(String name) {
  19. this.name = name;
  20. }
  21. public String getAge() {
  22. return age;
  23. }
  24. public void setAge(String age) {
  25. this.age = age;
  26. }
  27. public String getCellPhone() {
  28. return cellPhone;
  29. }
  30. public void setCellPhone(String cellPhone) {
  31. this.cellPhone = cellPhone;
  32. }
  33. @Override
  34. public String toString() {
  35. return "Employee{" +
  36. "name='" + name + '\'' +
  37. ", age='" + age + '\'' +
  38. ", cellPhone='" + cellPhone + '\'' +
  39. '}';
  40. }
  41. }
  1. import org.hibernate.validator.constraints.Length;
  2. import org.hibernate.validator.constraints.NotEmpty;
  3. import javax.validation.Valid;
  4. import java.util.List;
  5. /**
  6. * 公司数据模型
  7. */
  8. public class Company {
  9. @NotEmpty(message = "公司名字不能为空")
  10. private String name;
  11. @Length(min = 2,max = 20,message = "地址信息必须在2到20个字符之间")
  12. private String address;
  13. @NotEmpty(message = "员工信息不能为空")
  14. @ListNotHasNull
  15. @Valid
  16. private List<Employee> employees;
  17. public Company() {
  18. }
  19. public Company(String name, String address, List<Employee> employees) {
  20. this.name = name;
  21. this.address = address;
  22. this.employees = employees;
  23. }
  24. public String getName() {
  25. return name;
  26. }
  27. public void setName(String name) {
  28. this.name = name;
  29. }
  30. public String getAddress() {
  31. return address;
  32. }
  33. public void setAddress(String address) {
  34. this.address = address;
  35. }
  36. public List<Employee> getEmployees() {
  37. return employees;
  38. }
  39. public void setEmployees(List<Employee> employees) {
  40. this.employees = employees;
  41. }
  42. @Override
  43. public String toString() {
  44. return "Company{" +
  45. "name='" + name + '\'' +
  46. ", address='" + address + '\'' +
  47. ", employees=" + employees +
  48. '}';
  49. }
  50. }

从上面的 Company 我们可以看见已经使用了自定义的 @ListNotHasNull 标签

Controller

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.validation.BindingResult;
  4. import org.springframework.web.bind.annotation.PostMapping;
  5. import org.springframework.web.bind.annotation.RequestBody;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import javax.servlet.http.HttpServletResponse;
  9. import javax.validation.Valid;
  10. /**
  11. * 公司Controller
  12. */
  13. @RestController
  14. @RequestMapping("/test")
  15. public class CompanyController {
  16. private Logger logger = LoggerFactory.getLogger(UserController.class);
  17. @PostMapping(value = "/company")
  18. public String postCompany(@Valid @RequestBody Company company, BindingResult result,HttpServletResponse response){
  19. if(result.hasErrors()){
  20. List<ObjectError> list = result.getAllErrors();
  21. StringBuilder stringBuilder = new StringBuilder();
  22. for(ObjectError error:list){
  23. stringBuilder.append("\n"+error.getDefaultMessage());
  24. }
  25. response.setStatus(HttpStatus.BAD_REQUEST.value());
  26. return stringBuilder.toString();
  27. }
  28. return "ok";
  29. }
  30. }

分组校验

当我们针对同一个model的校验不一样的时候该怎么办?比如针对同一个model的添加和修改的时候?这个时候我们就需要采用分组验证。

定义分别代表修改和添加校验规则的接口

  1. /**
  2. * person模型新增时的参数校验规则
  3. */
  4. public interface PersonAddView {
  5. }
  1. /**
  2. * person模型修改时的参数校验规则
  3. */
  4. public interface PersonUpdateView {
  5. }

在数据模型上添加相应的校验规则

  1. import org.hibernate.validator.constraints.Length;
  2. import javax.validation.constraints.Max;
  3. import javax.validation.constraints.Min;
  4. import javax.validation.constraints.NotNull;
  5. /**
  6. * person数据模型
  7. */
  8. public class Person {
  9. private Long id;
  10. /**
  11. * 针对验证标签里面添加groups说明仅仅在特定的验证规则里面起作用,如果不加,那么就在默认的验证规则里面起作用。
  12. */
  13. @NotNull(groups = {PersonAddView.class},message = "添加的姓名不能为空")
  14. @Length(min = 2,max = 10,groups = {PersonUpdateView.class},message = "修改时的姓名必须在2到10个字符之间")
  15. private String name;
  16. @NotNull(groups = {PersonAddView.class}, message = "添加用户时地址不能为空")
  17. private String address;
  18. @Min(value = 18, groups = {PersonAddView.class}, message = "年龄不能低于18岁")
  19. @Max(value = 30, groups = {PersonUpdateView.class}, message = "年龄不能超过30岁")
  20. private Integer age;
  21. public Person() {
  22. }
  23. public Person(Long id, String name, String address, Integer age) {
  24. this.id = id;
  25. this.name = name;
  26. this.address = address;
  27. this.age = age;
  28. }
  29. public Long getId() {
  30. return id;
  31. }
  32. public void setId(Long id) {
  33. this.id = id;
  34. }
  35. public String getName() {
  36. return name;
  37. }
  38. public void setName(String name) {
  39. this.name = name;
  40. }
  41. public String getAddress() {
  42. return address;
  43. }
  44. public void setAddress(String address) {
  45. this.address = address;
  46. }
  47. public Integer getAge() {
  48. return age;
  49. }
  50. public void setAge(Integer age) {
  51. this.age = age;
  52. }
  53. @Override
  54. public String toString() {
  55. return "Person{" +
  56. "id=" + id +
  57. ", name='" + name + '\'' +
  58. ", address='" + address + '\'' +
  59. ", age=" + age +
  60. '}';
  61. }
  62. }

给参数添加校验

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.validation.BindingResult;
  4. import org.springframework.validation.annotation.Validated;
  5. import org.springframework.web.bind.annotation.*;
  6. import javax.servlet.http.HttpServletResponse;
  7. import javax.validation.groups.Default;
  8. /**
  9. * 人员Controller
  10. */
  11. @RestController
  12. @RequestMapping("/test")
  13. public class PersonController {
  14. private Logger logger = LoggerFactory.getLogger(PersonController.class);
  15. /**
  16. * 处@Validated(PersonAddView.class) 表示使用PersonAndView这套校验规则,若使用@Valid 则表示使用默认校验规则,
  17. * 若两个规则同时加上去,则只有第一套起作用
  18. *
  19. * @param person
  20. */
  21. @PostMapping(value = "/person")
  22. public String addPerson(@RequestBody @Validated({PersonAddView.class, Default.class}) Person person, BindingResult result,HttpServletResponse response) {
  23. logger.debug("enter post person");
  24. logger.debug("the information of person :"+ person);
  25. return ValidateUtility.judgeValidate(result,response);
  26. }
  27. /**
  28. * 修改Person对象
  29. * 此处启用PersonModifyView 这个验证规则
  30. * @param person
  31. */
  32. @PutMapping(value = "/person")
  33. public String modifyPerson(@RequestBody @Validated(value = {PersonUpdateView.class}) Person person, BindingResult result,HttpServletResponse response) {
  34. logger.debug("enter put person");
  35. logger.debug("the information of person :"+ person);
  36. if(result.hasErrors()){
  37. List<ObjectError> list = result.getAllErrors();
  38. StringBuilder stringBuilder = new StringBuilder();
  39. for(ObjectError error:list){
  40. stringBuilder.append("\n"+error.getDefaultMessage());
  41. }
  42. response.setStatus(HttpStatus.BAD_REQUEST.value());
  43. return stringBuilder.toString();
  44. }
  45. return "ok";
  46. }
  47. }

Controller 方法中的非对象参数验证

如果我们传入的参数没必要进行封装成对象,那么这个时候对参数的校验就得使用参数验证了。

添加参数验证配置类

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.http.HttpStatus;
  3. import org.springframework.stereotype.Component;
  4. import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
  5. import org.springframework.web.bind.annotation.ControllerAdvice;
  6. import org.springframework.web.bind.annotation.ExceptionHandler;
  7. import org.springframework.web.bind.annotation.ResponseBody;
  8. import org.springframework.web.bind.annotation.ResponseStatus;
  9. import javax.validation.ValidationException;
  10. /**
  11. * 参数验证配置类
  12. */
  13. @ControllerAdvice
  14. @Component
  15. public class GlobalExceptionHandler {
  16. @Bean
  17. public MethodValidationPostProcessor methodValidationPostProcessor() {
  18. return new MethodValidationPostProcessor();
  19. }
  20. @ExceptionHandler
  21. @ResponseBody
  22. @ResponseStatus(HttpStatus.BAD_REQUEST)
  23. public String handle(ValidationException exception) {
  24. return "传入参数不符合要求";
  25. }
  26. }

在 Controller 上添加参数验证

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.http.HttpStatus;
  4. import org.springframework.validation.annotation.Validated;
  5. import org.springframework.web.bind.annotation.*;
  6. import javax.validation.constraints.Min;
  7. /**
  8. * 员工Controller
  9. */
  10. @RestController
  11. @RequestMapping("/test")
  12. @Validated
  13. public class EmployeeController {
  14. private Logger logger = LoggerFactory.getLogger(EmployeeController.class);
  15. @GetMapping("/employee")
  16. @ResponseStatus(HttpStatus.OK)
  17. public @ResponseBody
  18. String check(@RequestParam @Min(value = 10,message = "名字长度必须大于10") int age) {
  19. logger.debug("enter employee get");
  20. return "ok";
  21. }
  22. }

统一异常处理

之前我们演示的都是在 Controller 中用 BindingResult 取校验的错误信息,在 Spring Boot 中默认的操作中,会向客户端返回一个 400 的状态码,并产生了一个 BindException 异常,我们可以拦截这个异常,这样就可以不用在每个需要检验参数的方法中取错误信息了。

  1. import com.yeskery.util.Result;
  2. import org.springframework.validation.BindException;
  3. import org.springframework.validation.ObjectError;
  4. import org.springframework.web.bind.annotation.ControllerAdvice;
  5. import org.springframework.web.bind.annotation.ExceptionHandler;
  6. import org.springframework.web.bind.annotation.ResponseBody;
  7. import java.util.List;
  8. /**
  9. * @author yeskery
  10. * @date 2018/06/02
  11. */
  12. @ControllerAdvice
  13. @ResponseBody
  14. public class GlobalExceptionHandler {
  15. @ExceptionHandler(value = BindException.class)
  16. public Result bindExceptionHandler(BindException e) {
  17. List<ObjectError> errors = e.getAllErrors();
  18. return Result.noData(errors.get(0).getDefaultMessage());
  19. }
  20. }

本文部分内容来自:

  1. https://blog.csdn.net/qq_28867949/article/details/78922520
  2. https://blog.csdn.net/ONROAD0612/article/details/72518467

评论

发表评论 点击刷新验证码

提示

该功能暂未开放