暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SpringBoot学习(五):验证器(hibernate-validator)使用

猿码记 2020-09-29
1951


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被约束的元素必须是falsevalidation-api
@AssertTrue被约束的元素必须是truevalidation-api
@DecimalMax被约束的元素必须是数字且其必须小于等于指定值validation-api
@DecimalMin被约束的元素是数字且其必须大于等于指定值validation-api
@Digits被约束的元素必须是数字且其在约束范围内validation-api
@Future被约束的元素是未来的时间validation-api
@Max被约束的元素是数字且其必须小于等于指定值validation-api
@Min被约束的元素是数字且其必须大于等于指定值validation-api
@NotNull被约束的原属不能为nullvalidation-api
@Null被约束的原始必须为nullvalidation-api
@Past被约束的元素必须是过去的时间validation-api
@Pattern被约束的元素必须符合自定正则表达式validation-api
@Size被约束的元素必须在范围内validation-api
@URL被约束的元素必须是一个URLhibernate-validator
@ScriptAssert验证类级别脚本hibernate-validator
@SafeHtml被约束的元素必须是一个HTMLhibernate-validator
@Range被约束的元素必须是在[min,max]范围hibernate-validator
@ParameterScriptAssert验证参数级别脚本hibernate-validator
@NotEmpty验证字符串、集合、字典或数组是否为null或者空hibernate-validator
@NotBlank验证字符串是否为null或空(支持去两端空字符)hibernate-validator
@Length验证字符串长度范围[min,max]hibernate-validator
@Email验证是否为emailhibernate-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());
    }
}



长按二维码关注最新动态

“阅读原文”我们一起进步
文章转载自猿码记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论