1. 安装依赖
在pom.xml
中添加
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
|
在 SpringBoot 2.3.x 以前 SpringBoot 包 默认引入 spring-boot-starter-validation 包,而自 SpringBoot 2.3.x 以后官方将其排除,需要单独引入。
2. 验证流程图
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 {
@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(); postProcessor.setValidator(validator()); return postProcessor; }
@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;
@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 {
@ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MissingServletRequestParameterException.class) public String handelMissingServletRequestParameterException(MissingServletRequestParameterException e, Model model) { log.error("缺少请求参数", e); String message = "缺少请求参数: " + e.getMessage(); HashMap<String, Object> stringObjectHashMap = new HashMap<>(); stringObjectHashMap.put("msg",message); stringObjectHashMap.put("code",400); return JSON.toJSONString(stringObjectHashMap); }
@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()); 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;
@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 {
@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); }
@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); }
@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()); }
@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()); } }
|