1. 安装依赖

pom.xml中添加

<!--验证器(validation) 开始部分 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--验证器(validation) 结束部分 -->

在 SpringBoot 2.3.x 以前 SpringBoot 包 默认引入 spring-boot-starter-validation 包,而自 SpringBoot 2.3.x 以后官方将其排除,需要单独引入。

2. 验证流程图

img

3. Hibernate的校验模式

Hibernate Validator有普通模式(默认是这个模式) 和 快速模式两种验证模式。

  • 普通模式: 会校验完所有的属性,然后返回所有的验证失败信息。
  • 快速模式: 只要有一个验证失败,则返回。

4. 添加Hibernate配置

配置hibernate Validator为快速模式

package com.hui.javalearn.config;


import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

@Configuration
public class ValidatorConfig {

/**
* 配置验证器
*
* @return Validator
*/
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 设置为快速返回模式
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}

5. 验证Get参数

5.1 配置方法验证器

Hibernate Validator是可以在方法级验证参数的,需要我们在Validator的配置中,添加MethodValidationPostProcessor Bean,完整配置如下:

package com.hui.javalearn.config;


import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

@Configuration
public class ValidatorConfig {

/**
* 设置方法参数验证器
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
// 设置validator模式为快速失败返回
postProcessor.setValidator(validator());
return postProcessor;
}
/**
* 配置验证器
* @return Validator
*/
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 设置为快速返回模式
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}

5.2 Get参数验证

GET 请求参数中一般是没有实体对象的,所以不能使用 @Valid,而是通过需要使用@Validated注解来使得验证生效。

package com.hui.javalearn.controller;

import com.hui.javalearn.common.ApiBaseController;
import com.hui.javalearn.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

/**
* @author liuqh
*/
@RestController
@Api(tags = "用户管理")
@RequestMapping("/user")
@Validated
public class UserController extends ApiBaseController {
@Autowired
private UserService userService;

@ApiOperation("登录")
@GetMapping("/login")
public String login(
@Pattern(regexp = "1[3|4|5|7|8][0-9]\\d{8}",message = "手机号格式不正确!")
@RequestParam("phone") String phone,
@NotEmpty(message = "密码不能为空") @RequestParam("pwd") String pwd
) {
return "phone:" + phone + " pwd: " + pwd;
}
}

@注意: 在要使用参数验证的类上一定要加上@Validated注解,否则无效

访问: http://127.0.0.1:8080/user/login

2020-09-14 15:46:19.770  WARN 15005 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'phone' is not present]

5.3 拦截错误

如果验证不通过,会抛出MissingServletRequestParameterException异常,我们可以在全局的异常处理器里面处理验证错误。

新增全局的异常处理器: GlobalExceptionHandler.java,代码如下:

package com.hui.javalearn.exception;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Set;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* 捕获参数异常处理
* @param e
* @param model
* @return String
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public String handelMissingServletRequestParameterException(MissingServletRequestParameterException e, Model model)
{
log.error("缺少请求参数", e);
String message = "缺少请求参数: " + e.getMessage();
// 临时使用map,方便测试。
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("msg",message);
stringObjectHashMap.put("code",400);
return JSON.toJSONString(stringObjectHashMap);
}

/**
* spring validator 方法参数验证异常拦截
*
* @param e 绑定验证异常
* @return 错误返回消息
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public String defaultErrorHandler(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
ConstraintViolation<?> violation = violations.iterator().next();
String message = violation.getMessage();
log.info("数据验证异常:{}", violation.getMessage());
// 临时使用map,方便测试。
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("msg",message);
stringObjectHashMap.put("code",400);
return JSON.toJSONString(stringObjectHashMap);
}
}

访问: http://127.0.0.1:8080/user/login

{
"msg": "Required String parameter 'phone' is not present",
"code": 400
}

6.验证POST参数(实体类)

接口上的Bean验证,需要在参数前加上@Valid或Spring的 @Validated注解,这两种注释都会导致应用标准Bean验证。如果验证不通过会抛出BindException异常,并变成400(BAD_REQUEST)响应。另外如果参数前有@RequestBody注解,验证错误会抛出MethodArgumentNotValidException异常。

6.1 定义实体类(dto)

package com.hui.javalearn.dto.param;

import com.hui.javalearn.utils.validate.Phone;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class UserParamDTO {

@ApiModelProperty(value = "用户名")
@NotBlank(message = "用户名不能为空")
private String nickName;

@Phone
@ApiModelProperty(value = "手机号")
private String phone;

@ApiModelProperty(value = "身份证号码")
@NotBlank(message = "身份证号码不能为空")
private String idCard;
}

6.2 编写Controller

package com.hui.javalearn.controller;

import com.hui.javalearn.common.ApiBaseController;
import com.hui.javalearn.common.ResponseResult;
import com.hui.javalearn.dto.param.UserParamDTO;
import com.hui.javalearn.model.UserModel;
import com.hui.javalearn.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.util.HashMap;
import java.util.List;

/**
* @author liuqh
*/
@RestController
@Api(tags = "用户管理")
@RequestMapping("/user")
@Validated
public class UserController extends ApiBaseController {
@Autowired
private UserService userService;
@ApiOperation("注册")
@PostMapping("/register")
public ResponseResult<HashMap<String, Integer>> register(@Valid UserParamDTO userParamDTO){
int i = userService.insertUser(userParamDTO);
HashMap<String, Integer> stringIntegerHashMap = new HashMap<>();
stringIntegerHashMap.put("id",i);
return ResponseResult.success(stringIntegerHashMap);
}
}

请求结果:

curl -X POST "http://127.0.0.1:8080/user/register" -H "Request-Origion:SwaggerBootstrapUi" -H "accept:*/*" -H "Content-Type:application/json" -d "nickName=张三" -d "phone=111" -d "pwd=11"

# 报错:
2020-09-14 18:15:02.595 WARN 17753 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors

6.3 拦截错误

修改全局的异常处理器GlobalExceptionHandler.java,修改的代码如下:

package com.hui.javalearn.exception;

import com.hui.javalearn.common.ResponseResult;
import com.hui.javalearn.common.enums.ResultEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* 捕获参数异常处理
*
* @param e
* @param model
* @return String
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseResult<?> handelMissingServletRequestParameterException(MissingServletRequestParameterException e, Model model) {
log.error("缺少请求参数", e);
String message = "缺少请求参数: " + e.getMessage();
return ResponseResult.error(ResultEnum.ERROR_PARAM.getCode(), message);
}

/**
* spring validator 方法参数验证异常拦截
*
* @param e 绑定验证异常
* @return 错误返回消息
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public ResponseResult<?> defaultErrorHandler(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
ConstraintViolation<?> violation = violations.iterator().next();
String message = violation.getMessage();
log.info("数据验证异常:{}", violation.getMessage());
return ResponseResult.error(ResultEnum.ERROR_PARAM.getCode(), message);
}

/**
* 拦截实体类参数(参数前没有@RequestBody注解)校验失败的错误
*
* @param e
* @return
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public ResponseResult<?> bindExceptionHandler(BindException e) {
ObjectError objectError = e.getAllErrors().get(0);
log.info("数据验证异常:{}", objectError.getDefaultMessage());
return ResponseResult.
error(ResultEnum.ERROR_PARAM.getCode(), objectError.getDefaultMessage());
}

/**
* 拦截实体类参数(参数前有@RequestBody注解)校验失败的错误
*
* @param e
* @return
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseResult<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return ResponseResult.
error(ResultEnum.ERROR_PARAM.getCode(),objectError.getDefaultMessage() );
}
}

ResponseResult是我自定义的公共类,你也可以像Get方式一样使用map做使用测试

7. 验证相关的注解

hibernate-validator沿用了validation-api中的所有注解约束,同时也定义了一些自己的约束:

constraint 描述 来源
@AssertFalse 被约束的元素必须是false validation-api
@AssertTrue 被约束的元素必须是true validation-api
@DecimalMax 被约束的元素必须是数字且其必须小于等于指定值 validation-api
@DecimalMin 被约束的元素是数字且其必须大于等于指定值 validation-api
@Digits 被约束的元素必须是数字且其在约束范围内 validation-api
@Future 被约束的元素是未来的时间 validation-api
@Max 被约束的元素是数字且其必须小于等于指定值 validation-api
@Min 被约束的元素是数字且其必须大于等于指定值 validation-api
@NotNull 被约束的原属不能为null validation-api
@Null 被约束的原始必须为null validation-api
@Past 被约束的元素必须是过去的时间 validation-api
@Pattern 被约束的元素必须符合自定正则表达式 validation-api
@Size 被约束的元素必须在范围内 validation-api
@URL 被约束的元素必须是一个URL hibernate-validator
@ScriptAssert 验证类级别脚本 hibernate-validator
@SafeHtml 被约束的元素必须是一个HTML hibernate-validator
@Range 被约束的元素必须是在[min,max]范围 hibernate-validator
@ParameterScriptAssert 验证参数级别脚本 hibernate-validator
@NotEmpty 验证字符串、集合、字典或数组是否为null或者空 hibernate-validator
@NotBlank 验证字符串是否为null或空(支持去两端空字符) hibernate-validator
@Length 验证字符串长度范围[min,max] hibernate-validator
@Email 验证是否为email hibernate-validator
@EAN 检测字符序列是否有效 hibernate-validator
@CreditCardNumber 验证身份认证是否有效 hibernate-validator

8.封装常用注解

8.1 手机号

创建验证手机号注解

package com.hui.javalearn.tools.annotation;

import org.hibernate.validator.constraints.CompositionType;
import org.hibernate.validator.constraints.ConstraintComposition;
import org.hibernate.validator.constraints.Length;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@ConstraintComposition(CompositionType.OR)
@Pattern(regexp = "1[3|4|5|6|7|8][0-9]\\d{8}")
@Null
@Length(min = 0, max = 0)
@Documented
@Constraint(validatedBy = {})
@Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@ReportAsSingleViolation
public @interface Phone {
String message() default "手机号校验错误";

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

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

使用注解

@PostMapping("/getUser")
// 使用
public ResponseResult<UserResponse> getUser(@Phone @RequestParam(value = "phone") String phone) {
try {
UserModel userModel = userService.searchUserByPhone(phone);
UserResponse userResponse = UserResponse.model2Response(userModel);
return ResponseResult.success(userResponse);
} catch (Exception e) {
return ResponseResult.error(ResultEnum.ERROR.getCode(), e.getMessage());
}
}