SpringBoot参数校验hibernate-validator使用

一、介绍

  如果我们的项目使用了Spring Boot,hibernate validator框架已经集成在 spring-boot-starter-web中,所以无需再添加其他依赖。如果不是Spring Boot项目,则需要添加如下依赖:

hibernate-validator参数检验不生效问题解决:主要是依赖的版本号大版本为6即可生效
https://blog.csdn.net/u012978272/article/details/120009157

<dependency>
	<groupId>org.hibernate.validator</groupId>
	<artifactId>hibernate-validator</artifactId>
	<version>6.2.5.Final</version>
</dependency>

常用注解介绍

注解 作用类型 来源 说明
@Null 任何类型 属性必须为null
@NotNull 任何类型 属性不能为null
@NotEmpty 集合 hibernate validator扩展注解 集合不能为null,且size大于0
@NotBlank 字符串、字符 hibernate validator扩展注解 字符类不能为null,且去掉空格之后长度大于0
@AssertTrue Boolean、boolean 布尔属性必须是true
@Min 数字类型(原子和包装) 限定数字的最小值(整型)
@Max 同@Min 限定数字的最大值(整型)
@DecimalMin 同@Min 限定数字的最小值(字符串,可以是小数)
@DecimalMax 同@Min 限定数字的最大值(字符串,可以是小数)
@Range 数字类型(原子和包装) hibernate validator扩展注解 限定数字范围(长整型)
@Length(min=,max=) 字符串 hibernate validator扩展注解 限定字符串长度
@Size 集合 限定集合大小
@Past 时间、日期 必须是一个过去的时间或日期
@Future 时期、时间 必须是一个未来的时间或日期
@Email 字符串 hibernate validator扩展注解 必须是一个邮箱格式
@Pattern 字符串、字符 正则匹配字符串

二、校验方式

1、单个参数校验

Controller类上必须添加@Validated注解(不加验证就不会生效),如下所示:

@Validated
@RestController
@RequestMapping("/user")
public class UserController {
  // do something...
}

方法参数前面加上注解即可, 如下所示:

import javax.validation.constraints.NotNull;
/**
 * 推荐这种方式
 * 缺少参数时会抛出异常:javax.validation.ConstraintViolationException
 */
public Result test1(@NotNull(message = "id不能为空") Long id) {
    // 
}


/**
 * 不推荐使用
 * @RequestParam 注解也会对参数进行校验
 * 如果参数id没传,则会抛出:javax.servlet.ServletException 异常
 * 这种情况想要跳过校验可添加 javax.annotation.Nullable 注解到参数上
 */
public Result test2(@RequestParam Long id) {
    // do something
}

/**
 * 不推荐使用
 * 此种情况不对 id 参数进行检查,传值就有,没传就是null
 */
public Result test3(Long id) {
    // do something
}

2、 对象参数校验

对象参数校验使用时,需要先在对象的校验属性上添加注解,然后在Controller方法的对象参数前添加@Validated注解,如下所示:

public Result addUser(@RequestBody @Validated UserDto userDto) {
    // do something
}

public class UserDto {
    @NotBlank(message="名称不能为空")
    private String name;
    @NotNull(message="年龄不能为空")
    private Integer age;
}

3、 嵌套对象校验

  如果需要校验的参数对象中还嵌套有一个对象属性,而该嵌套的对象属性也需要校验,那么就需要在该对象属性上增加@Valid注解。

public class UserDto {
    @NotNull
    private Long id;

    @NotBlank
    private String name;

    @NotNull
    private Integer age;

    @Valid
    private Phone phone;
}

public class Phone {
    @NotBlank
    private String type;
    
    @NotBlank
    private String phoneNum;
}

4、分组校验

  在对象参数校验场景下,有一种特殊场景,同一个参数对象在不同的场景下有不同的校验规则。比如,在创建对象时不需要传入id字段(id字段是主键,由系统生成,不由用户指定),但是在修改对象时就必须要传入id字段。在这样的场景下就需要对注解进行分组。

1)组件默认有个分组Default.class, 所以我们可以再创建一个分组例如UpdateAction.class,如下所示:

public interface UpdateAction {
    
}

2)在参数类中需要校验的属性上,在注解中添加groups属性(表示Default.class默认情况下会校验name,age参数,而在UpdateAction.class情况下会校验id参数),如下所示:

public class UserDto {
    @NotNull(groups = {UpdateAction.class}, message = "id不能为空")
    private Long id;
    
    @NotBlank
    private String name;

    @NotNull
    private Integer age;
}

3)在Controller的方法中,在@Validated注解里指定哪种场景即可(没有指定就代表采用Default.class,采用其他分组则需要显示指定)。如下代码便表示在addUser()接口中按照默认情况进行参数校验,在updateUser()接口中按照默认情况和UpdateAction分组对参数进行共同校验。

public Result addUser(@Validated UserDto userDto) {
  // do something
}

public Result updateUser(@Validated({Default.class, UpdateAction.class}) UserDto userDto) {
  // do something
}

5、枚举校验

枚举校验hibernate validator并不支持。需要自己扩展实现

1) 定义EnumChck注解,如下所示:

@Documented
@Constraint(validatedBy = EnumConstraintValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(EnumCheck.List.class)
public @interface EnumCheck {
    String message() default "{javax.validation.constraints.EnumCheck.message}";

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

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

    /**
     * 枚举类
     *
     */
    Class<? extends EnumValidator> clazz();

    /**
     * 调用的方法名称
     */
    String method() default "getValue";

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        EnumCheck[] value();
    }
}

2) 定义EnumValidator接口,让需要校验的枚举类实现其接口的getValue()方法,如下所示:

public interface EnumValidator {
    Object getValue();
}

3)自定义实现EnumConstraintValidator,需要实现ConstraintValidator接口,如下所示:

public class EnumConstraintValidator implements ConstraintValidator<EnumCheck, Object> {
    /**
         * 注解对象
         */
    private EnumCheck annotation;

    /**
         * 初始化方法
         *
         * @param constraintAnnotation 注解对象
         */
    @Override
    public void initialize(EnumCheck constraintAnnotation) {
        this.annotation = constraintAnnotation;
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (Objects.isNull(value)) {
            return false;
        }

        Object[] enumConstants = annotation.clazz().getEnumConstants();
        try {
            Method method = annotation.clazz().getMethod(annotation.method());
            for (Object enumConstant : enumConstants) {
                if (value.equals(method.invoke(enumConstant))) {
                    return true;
                }
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        return false;
    }
}

4) 具体使用步骤如下:

具体枚举类实现上面定义的EnumValidator接口:

public enum RefNodeType implements EnumValidator {
    PROJECT("project", "项目"),
    FIELD("field", "变量"),
    FUNC("func", "函数");

    private final String code;
    private final String label;

    RefNodeType(String code, String label) {
        this.code = code;
        this.label = label;
    }

    public String getCode() {
        return code;
    }

    public String getLabel() {
        return label;
    }

    @Override // 需要实现getValue方法
    public Object getValue() {
        return code;
    }
}

参数校验加上@EnumCheck枚举校验注解,如下所示:

public class TestDto {
    @NotBlank(message = "变量类型不能为空")
    // 加上枚举校验注解
    @EnumCheck(clazz = RefNodeType.class, message = "变量类型不合法")   
    private String sourceType;
}

6、正则通用校验

1)定义RegRule接口,将需要用到的正则表达式定义为接口属性,如下所示:

public interface RegRule {
    String MOBILE = "^(((13[0-9])|(14[579])|(15([0-3]|[5-9]))|(16[6])|(17[0135678])|(18[0-9])|(19[89]))\\d{8})$";
}

2)对象属性加上@Pattern注解,如下所示:

public class UserDto {
    @Pattern(regexp = RegRule.MOBILE, message = "手机号格式不正确")
    private String mobile;
}

三、各类异常捕获处理

参数校验失败后会抛出异常,只需要在全局异常处理类中捕获参数校验的失败异常,然后将错误消息添加到返回值中即可。捕获异常的方法如下所示,返回值AjaxResult是我们系统自定义的返回值类。

注意:如果存在实现了WebMvcConfigurer的配置类,并且重写了configureHandlerExceptionResolvers方法,那么异常信息将会在configureHandlerExceptionResolvers方法中被捕获处理,就不会走此处的逻辑

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author JHL
 * @version 1.0
 * @since : JDK 11
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 全局异常处理
     */
    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e, HttpServletRequest request, HttpServletResponse response) {
        // do something
        AjaxResult result = null;
    }
}

1)缺少参数抛出的异常是MissingServletRequestParameterException,如下所示:

if (e instanceof MissingServletRequestParameterException) {
    String msg = MessageFormat.format("缺少参数{0}", ((MissingServletRequestParameterException) e).getParameterName());
    result = AjaxResult.error(HttpStatus.HTTP_BAD_REQUEST, msg);
}

2)单参数校验失败后抛出的异常是ConstraintViolationException,如下所示:

if (e instanceof ConstraintViolationException){
    String msg = e.getMessage().split(":")[1].trim();
    result = AjaxResult.error(HttpStatus.HTTP_BAD_REQUEST, msg);
}

3)GET请求的对象参数校验失败后抛出的异常是 BindException,如下所示:

if (e instanceof BindException){
    result = AjaxResult.error(HttpStatus.HTTP_BAD_REQUEST, e.getMessage());
}

4)POST请求的对象参数校验失败后抛出的异常是MethodArgumentNotValidException,如下所示:

if (e instanceof MethodArgumentNotValidException){
    // 四个中文以上的异常信息才会被匹配
    String msg = ReUtil.findAll("[\u4e00-\u9fa5]{4,}", e.getMessage(), 0).get(0);
    result = new AjaxResult(HttpStatus.HTTP_BAD_REQUEST, msg);
}

原文地址:

https://www.cnblogs.com/itsharehome/p/15425885.html

@Validated注解不生效问题:https://blog.csdn.net/qiuxuezhe_fei/article/details/128197714

posted @     阅读(647)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2021-06-28 postman测试上传文件接口
点击右上角即可分享
微信分享提示