spring-boot中使用hibernate validation 进行传参校验

本文详细介绍了一个Java Web项目中的表单验证实现方法,包括引入依赖、异常处理、请求参数接收、自定义校验器、分组校验及常用验证注解等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文是根据kdyzm项目的代码自测整合的具体的可看原项目地址

一:项目依赖

        <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>7.0.1.Final</version>
        </dependency>

二:配置请求异常处理

在这里插入图片描述

返回DTO

import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
public class ResultDto implements Serializable {
    private Integer code;
    private String msg;
    private Object data;
    public static ResultDto success(String msg){
        return new ResultDto().setCode(2000).setMsg(msg);
    }
    public static ResultDto error(String msg){
        return new ResultDto().setCode(5000).setMsg(msg);
    }
}


在这里插入代码片

注解

注解说明
@Null被注解的元素必须为空
@NotNull被注解的元素必须不为空
@AssertTrue被注解的元素必须为true
@AssertFlase被注解的元素必须为false
@Min(value)被注解的元素必须是数字,且必须大于指定的最小值
@Max(value)被注解的元素必须是数字,且必须小于指定的最大值
@DecimalMin(value)被注解的元素必须是数字,且必须大于指定的最小值
@DecaimalMax(value)被注解的元素必须是数字,且必须小于指定的最大值
@Size(max=,min=)被注解元素的大小必须在指定的范围内
@Digit(integer,fraction)被注解元素必须是数字,且其值必须在可接受的范围内
@Past被注解元素必须是一个过去的日期 yyyy/MM/dd 其他格式识别不出来
@Futrue被注解元素必须是一个将来的日期 yyyy/MM/dd 其他格式识别不出来
@Pattern(regex=,flag=)被注解元素必须符合指定的正则表达式
@NotBlank验证非空,且长度必须大于0
@Email被注解的元素必须是电子邮件地址
@Length(max=,min=)被注解的字符串大小必须在指定的范围内
@NotEmpty被注解的字符串必须非空
@Range(max=,min=)被注解的元素必须在指定范围内

定义请求异常拦截

经过测试(或许是配置问题)部分异常调用的全局异常处理,这样就失去了意义,但是大部分的异常属于BindException

import com.validation.demo.ResultDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 请求异常拦截
 */
@RestControllerAdvice
@Slf4j
public class ValidationAdvice {

    //创建全局异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResultDto handler(Exception e) {
        //获取异常信息,获取异常堆栈的完整异常信息
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        return  ResultDto.error("服务异常,请稍后再试");
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public Object handler(ConstraintViolationException e) {
        StringBuffer errorMsg = new StringBuffer();
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        violations.forEach(x -> errorMsg.append(x.getMessage()).append(";"));
        return ResultDto.error(errorMsg.toString()) ;
    }

    //处理校验异常,对于对象类型的数据的校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public Object handler(MethodArgumentNotValidException e) {
        StringBuffer sb = new StringBuffer();
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));
        return ResultDto.error(sb.toString()) ;
    }
    //会出现拦截到参数异常但是还是调用全局异常的问题 使用BindException来获取错误信息
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public Object handler(BindException e) {
        StringBuffer sb = new StringBuffer();
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        allErrors.forEach(i->{
            sb.append(i.getDefaultMessage()+";");
        });
        return ResultDto.error(sb.toString()) ;
    }
}

三:接参请求

单个参数

在这里插入图片描述
在这里插入图片描述

Bean对象

import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.*;
import java.util.Date;
import java.util.List;

@Data
@Accessors
public class TestBean {
    @NotNull(message = "用户id不能为空")
    @NotEmpty(message ="用户id不能为空" )
    private String name;

    @NotEmpty(message = "手机号不能为空")
    private String mobile;

    @NotEmpty(message = "电子邮箱不能为空")
    @Email(message = "电子邮箱格式不正确")
    private String email;
//
    @NotNull(message = "年龄不能为空")
    @Min(value = 12, message = "允许注册年龄最小为12岁")
    @Max(value = 24, message = "允许年龄最大为24岁")
    private Integer age;

    @AssertTrue
    private Boolean isTrue;

    @DecimalMin(value = "45",message = "数值必须大于45")
    private Integer number;

    @Past(message = "必须是过去的日期")
    private Date pastDate;

    @NotEmpty(message = "联系人不允许为空")
    @Size(min = 1, max = 3, message = "联系人长度只允许1到3之间")
    private List<String> contacts;
}

在这里插入图片描述

在这里插入图片描述

{
  "code": 2000,
  "msg": "TestBean(name\u003ddemoData, mobile\u003ddemoData, email\u003ddemoData@163.com, age\u003d12, isTrue\u003dtrue, number\u003d45, pastDate\u003dMon May 24 00:00:00 CST 2021, contacts\u003d[1, 2, 3])"
}

在这里插入图片描述

{
  "code": 5000,
  "msg": "只能为true;数值必须大于45;
  允许注册年龄最小为12;联系人长度只允许13之间;
  电子邮箱格式不正确;必须是过去的日期;"
}

四:自定义校验器

配置注解


public class ValidationUtilFun {
    //手机号校验正则
    public static final String MOBILE_REGX = "^[1][3-9][0-9]{9}$";

    public static final String MOBILE_MSG = "手机号格式错误";

    //用户名校验正则
    public static final String USERNAME_REGX = "^[a-zA-z]\\w{4,19}$";

    public static final String USERNAME_MSG = "账号必须是字母开头,字母、数字、下划线组成,4-20位";
}


import com.validation.demo.validationConfig.utils.ValidationUtilFun;
import com.validation.demo.validationConfig.validator.MobileValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Pattern;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 整个代码可以copy  validation自带的@Email注解
 */
@Documented
//代表处理逻辑是 MobileValidator这个类
@Constraint(validatedBy = {MobileValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface Mobile {
    //指定默认的返回文字
    String message() default ValidationUtilFun.MOBILE_MSG;

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

    Class<? extends Payload>[] payload() default {};
    //指定正则验证
    String regexp() default ValidationUtilFun.MOBILE_REGX;

    Pattern.Flag[] flags() default {};

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

配置验证逻辑

import com.validation.demo.validationConfig.annotation.Mobile;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class MobileValidator implements ConstraintValidator<Mobile, String> {
    private String regexp;
    @Override
    public void initialize(Mobile constraintAnnotation) {
        //获取校验的手机号的格式
        this.regexp = constraintAnnotation.regexp();
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (!StringUtils.hasText(value)) {
            return true;
        }
        return value.matches(regexp);
    }
}

Bean中使用

@Data
@Accessors
public class TestBean {
//    @NotNull(message = "用户id不能为空")
//    @NotEmpty(message ="用户id不能为空" )
//    private String name;

    @NotEmpty(message = "手机号不能为空")
    @Mobile(message = "手机号格式不正确")
    private String mobile;

在这里插入图片描述{ "code": 5000, "msg": "手机号格式不正确;" }
在这里插入图片描述

{
  "code": 2000,
  "msg": "TestBean(mobile\u003d18141111545)"
}

五:分组校验

这里举个例子 APP的注册方式有1.用户名 2.手机号 3.Email
在使用用户名注册时 不希望手机号 Email验证生效

创建接口

在这里插入图片描述

public interface ValidEmail {
}

public interface ValidMobile {
}

public interface ValidUserName {
}

bean中指定使用的分组

字段指定了使用的分组类型,可指定多个

    @NotNull(message = "用户id不能为空",groups ={ ValidUserName.class})
    @UserName(groups ={ ValidUserName.class})
    private String name;

    @NotEmpty(message = "手机号不能为空",groups ={ ValidMobile.class})
    @Mobile(message = "手机号格式不正确",groups ={ ValidMobile.class})
    private String mobile;

    @NotEmpty(message = "电子邮箱不能为空",groups ={ ValidEmail.class})
    @Email(message = "电子邮箱格式不正确",groups ={ ValidEmail.class})
    private String email;

    @NotNull(message = "年龄不能为空",groups ={ ValidUserName.class,ValidMobile.class,ValidEmail.class})
    @Min(value = 12, message = "允许注册年龄最小为12岁",groups ={ ValidUserName.class,ValidMobile.class,ValidEmail.class})
    @Max(value = 24, message = "允许年龄最大为24岁",groups ={ ValidUserName.class,ValidMobile.class,ValidEmail.class})
    private Integer age;

控制层接口指定使用的分组

在注解指定使用的分组即可

    /**
     * 模拟用户名注册
     */
    @RequestMapping("/regUserName")
    public ResultDto regUserName(@Validated(ValidUserName.class) TestBean testBean){
       return ResultDto.success(testBean.toString());
    }

    /**
     * 模拟手机注册
     */
    @RequestMapping("/regMobile")
    public ResultDto regMobile(@Validated(ValidMobile.class) TestBean testBean){
        return ResultDto.success(testBean.toString());
    }

    /**
     * 模拟邮箱注册
     */
    @RequestMapping("/regEmail")
    public ResultDto regEmail(@Validated(ValidEmail.class) TestBean testBean){
        return ResultDto.success(testBean.toString());
    }

用户名接口

在参数中未传入手机号和邮箱验证时根据分组进行校验

在这里插入图片描述

{
  "code": 2000,
  "msg": "TestBean(name\u003ddemoData, mobile\u003dnull, email\u003dnull, age\u003d12)"
}

在这里插入图片描述

{
  "code": 5000,
  "msg": "允许注册年龄最小为12岁;"
}

手机号接口

在这里插入图片描述

{
  "code": 2000,
  "msg": "TestBean(name\u003dnull, mobile\u003d15548488484, email\u003dnull, age\u003d12)"
}

六:验证注解

// 空和非空检查: @Null、@NotNull、@NotBlank、@NotEmpty
@Null(message = "验证是否为 null")
private Integer isNull;

@NotNull(message = "验证是否不为 null, 但无法查检长度为0的空字符串")
private Integer id;

@NotBlank(message = "检查字符串是不是为 null,以及去除空格后长度是否大于0")
private String name;
          
@NotEmpty(message = "检查是否为 NULL 或者是 EMPTY")
private List<String> stringList;
          
// Boolean值检查: @AssertTrue、@AssertFalse
@AssertTrue(message = " 验证 Boolean参数是否为 true")
private Boolean isTrue;
          
@AssertFalse(message = "验证 Boolean 参数是否为 false ")
private Boolean isFalse;
          
// 长度检查: @Size、@Length
@Size(min = 1, max = 2, message = "验证(Array,Collection,Map,String)长度是否在给定范围内")
private List<Integer> integerList;
      
@Length(min = 8, max = 30, message = "验证字符串长度是否在给定范围内")
private String address;
      
// 日期检查: @Future、@FutureOrPresent、@Past、@PastOrPresent
@Future(message = "验证日期是否在当前时间之后")
private Date futureDate;
      
@FutureOrPresent(message = "验证日期是否为当前时间或之后")
private Date futureOrPresentDate;
      
@Past(message = "验证日期是否在当前时间之前")
private Date pastDate;
      
@PastOrPresent(message = "验证日期是否为当前时间或之前")
private Date pastOrPresentDate;
      
// 其它检查: @Email、@CreditCardNumber、@URL、@Pattern、
@ScriptAssert、@UniqueElements
@Email(message = "校验是否为正确的邮箱格式")
private String email;
      
@CreditCardNumber(message = "校验是否为正确的信用卡号")
private String creditCardNumber;
      
@URL(protocol = "http", host = "127.0.0.1", port = 8080, message= "校验是否为正确的URL地址")
private String url;
      
@Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "正则校验是否为正确的手机号")
private String phone;
         
// 对关联对象元素进行递归校验检查
@Valid
@UniqueElements(message = "校验集合中的元素是否唯一")
private List<CalendarEvent> calendarEvent;

@Data
@ScriptAssert(lang = "javascript", script ="_this.startDate.before(_this.endDate)",message = "通过脚本表达式校验参数")
private class CalendarEvent {
  private Date startDate;
  private Date endDate;
}

// 数值检查: @Min、@Max、@Range、@DecimalMin、@DecimalMax、@Digits
@Min(value = 0, message = "验证数值是否大于等于指定值")
@Max(value = 100, message = "验证数值是否小于等于指定值")
@Range(min = 0, max = 100, message = "验证数值是否在指定值区间范围内")
private Integer score;

@DecimalMin(value = "10.01", inclusive = false, message = "验证数值是否大于等于指定值")
@DecimalMax(value = "199.99", message = "验证数值是否小于等于指定值")
@Digits(integer = 3, fraction = 2, message = "限制整数位最多为3,小数位最多为2")
private BigDecimal money;
### 解决 Spring Boot Starter Validation 报错问题 当遇到 `spring-boot-starter-validation` 相关的错误时,通常是因为配置不当或依赖缺失。以下是几种常见的解决方案: #### 1. 添加必要的依赖项 确保项目中包含了 `spring-boot-starter-validation` 和其他可能需要的相关库。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> ``` 如果使用的是 Hibernate Validator,则还需要添加相应的依赖[^1]。 #### 2. 配置验证注解 对于实体类字段上的约束条件,应正确应用 JSR-303 或者 Bean Validation API 提供的标准注解,例如 `@NotNull`, `@Size`, `@Email` 等。 ```java public class User { @NotBlank(message = "Name is mandatory") private String name; @Email(message = "Invalid email format") private String email; } ``` 这些注解可以帮助在运行期间自动触发参数校验逻辑,并返回友好的提示信息给前端调用方[^2]。 #### 3. 处理异常响应 为了更好地管理来自控制器层面上发生的 ConstraintViolationException 类型的全局异常,在应用程序入口处定义一个统一的异常处理器是非常有帮助的做法。 ```java @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Object> handleValidationExceptions( MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getAllErrors().forEach((error) ->{ String fieldName = ((FieldError) error).getField(); String errorMessage = error.getDefaultMessage(); errors.put(fieldName, errorMessage); }); return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); } } ``` 此方法能够捕获所有由表单提交引起的验证失败事件,并将其转换成易于理解的消息体发送回客户端[^3]。 通过以上措施可以有效减少因缺少适当设置而导致的各种潜在问题的发生几率。当然实际开发过程中还可能会碰到更多复杂情况,这时就需要具体分析具体的业务场景来做出调整了。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值