Springboot参数分组校验

Springboot参数分组校验

文章目录

简介

Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email@Length等。

Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。本文基于 JDK21 和 springboot3.1.5 进行整理。


代码准备

参数校验对象,以下实例都基于该对象进行。

java 复制代码
package com.lzhch.practice.dto.req;

import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import java.io.Serial;
import java.io.Serializable;
import java.util.Date;

/**
 * 参数分组校验入参
 * <p>
 * author: lzhch
 * version: v1.0
 * date: 2023/11/20 15:36
 */

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ParamGroupValidatedReq implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     * 内部定义接口和统一定义接口任选其一即可
     */
    // @NotNull(message = "用户id不能为空", groups = ParamGroupValidated.Create.class)
    @NotNull(message = "用户id不能为空") // Service 层不进行分组校验
    // @NotNull(message = "用户id不能为空", groups = ParamGroupValidatedReq.Save.class)
    private Long userId;

    /**
     * 用户名
     */
    @NotBlank(message = "用户名不能为空")
    @Length(max = 20, message = "用户名不能超过20个字符")
    private String username;

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

    /**
     * 性别
     */
    private String sex;

    /**
     * 邮箱
     */
    @NotBlank(message = "联系邮箱不能为空")
    @Email(message = "邮箱格式不对")
    private String email;

    /**
     * 密码
     */
    private String password;

    /**
     * 创建时间
     */
    // @Future(message = "时间必须是将来时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;


    /**
     * 保存的时候校验分组
     */
    public interface Save {
    }

    /**
     * 更新的时候校验分组
     */
    public interface Update {
    }

}

分组接口

java 复制代码
import jakarta.validation.groups.Default;

/**
 * 新增参数校验接口
 * <p>
 * author: lzhch
 * version: v1.0
 * date: 2023/11/20 17:20
 */

public interface ParamGroupValidated {

    /**
     * 在声明分组的时候加上 extend javax.validation.groups.Default
     * 否则, 在你声明 @Validated(Update.class)的时候, 就会出现你在默认没添加 groups = {} 的时候
     * 校验组 @Email(message = "邮箱格式不对") 会不去校验, 因为默认的校验组是 groups = {Default.class}.
     */

    interface Create extends Default {
    }

    interface Update extends Default {
    }

}

全局异常捕捉,参数校验报错分为 MethodArgumentNotValidException 和 ConstraintViolationException。

java 复制代码
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Objects;

/**
 * 全局异常处理
 * <p>
 * author: lzhch
 * version: v1.0
 * date: 2023/11/20 17:51
 */

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * Controller 层参数校验
     *
     * @param methodArgumentNotValidException: Controller 层参数校验失败异常类型
     * @return 统一封装的结果类, 含有代码code和提示信息msg
     * Author: lzhch 2023/11/21 15:13
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String handleMethodArgumentNotValidException(MethodArgumentNotValidException methodArgumentNotValidException) {
        log.error(methodArgumentNotValidException.getMessage(), methodArgumentNotValidException);
        FieldError fieldError = methodArgumentNotValidException.getBindingResult().getFieldError();
        if (Objects.isNull(fieldError)) {
            return methodArgumentNotValidException.getMessage();
        }

        return fieldError.getDefaultMessage();
    }

    /**
     * 捕获并处理未授权异常
     *
     * @param e: Service 层参数校验失败异常类型
     * @return 统一封装的结果类, 含有代码code和提示信息msg
     * Author: lzhch 2023/11/21 15:13
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public String handleConstraintViolationException(ConstraintViolationException e) {
        return String.join(";", e.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessageTemplate)
                .toList());
    }

}

单个或多个参数的校验

比如根据 Id 查询、删除等,无需封装成对象,且无需使用 JSON 格式。

java 复制代码
import com.alibaba.fastjson2.JSON;
import com.lzhch.practice.dto.req.ParamGroupValidatedReq;
import com.lzhch.practice.service.IParamGroupValidatedService;
import com.lzhch.practice.validatedtype.ParamGroupValidated;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotBlank;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 参数分组校验 controller
 * <p>
 * author: lzhch
 * version: v1.0
 * date: 2023/11/20 17:22
 */

@Slf4j
// 用于在 Controller 层的简单校验, 比如 simple 方法
@Validated
@RestController
@RequestMapping(value = "validated/paramGroup")
public class ParamGroupValidatedController {

    @Resource
    private IParamGroupValidatedService paramGroupValidatedService;

    /**
     * 简单校验
     * 必须在 Controller 上添加 @Validated 注解
     * 指定 groups 可以进行分组校验
     */
    @GetMapping(value = "simple")
    public void simple(@NotBlank(message = "username 不能是空的啊!!!", groups = ParamGroupValidated.Create.class) String username) {
        log.info("result {}", username);
    }

}

非 JSON 格式的对象参数校验

Controller 和方法参数上需要添加 @Validated 注解;

对象里面增加相应类型的校验注解。

java 复制代码
/**
 * 非 JSON 格式的对象校验
 * 使用 @Validated 注解的 value 属性指定分组
 */
@GetMapping(value = "simple1")
public void simple1(@Validated ParamGroupValidatedReq paramGroupValidatedReq) {
    log.info("result {}", JSON.toJSONString(paramGroupValidatedReq));
}

JSON 格式的对象参数校验

不需要在 Controller 上添加 @Validated 注解

JSON 格式校验只需要增加 @RequestBody 注解。

java 复制代码
/**
 * 统一接口分组测试新增
 * 使用 @Validated 注解的 value 属性指定分组
 */
@PostMapping(value = "create")
public void create(@RequestBody @Validated(value = ParamGroupValidated.Create.class) ParamGroupValidatedReq paramGroupValidatedReq) {
    log.info("result {}", JSON.toJSONString(paramGroupValidatedReq));
}

Service 层校验

以上为在 Controller 里面进行的校验,接下来是 Service 层的校验代码。

接口:

在接口中必须添加 @Valid 以及 @NotBlank 等注解, 否则报错

java 复制代码
package com.lzhch.practice.service;

import com.lzhch.practice.dto.req.ParamGroupValidatedReq;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;

/**
 * 分组校验接口
 * <p>
 * author: lzhch
 * version: v1.0
 * date: 2023/11/20 18:16
 */

public interface IParamGroupValidatedService {

    // 在接口中必须添加 @Valid 以及 @NotBlank 等注解, 否则报错

    /**
     * 字段校验
     */
    void filedValidated(@NotBlank(message = "用户名不能为空") String username);

    /**
     * 不分组校验
     */
    void create(@Valid ParamGroupValidatedReq paramGroupValidatedReq);

    /**
     * 分组校验
     */
    // @Validated(value = ParamGroupValidated.Create.class)
    void create1(@Valid ParamGroupValidatedReq paramGroupValidatedReq);

}

实现类:

必须给实现类添加 @Validated 注解!

java 复制代码
import com.alibaba.fastjson2.JSON;
import com.lzhch.practice.dto.req.ParamGroupValidatedReq;
import com.lzhch.practice.service.IParamGroupValidatedService;
import com.lzhch.practice.validatedtype.ParamGroupValidated;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

/**
 * 分组校验接口实现类
 * <p>
 * author: lzhch
 * version: v1.0
 * date: 2023/11/20 18:16
 */

@Slf4j
@Service
// 在接口上添加 @Validated 注解, 对该类进行参数校验
@Validated
public class ParamGroupValidatedServiceImpl implements IParamGroupValidatedService {

    /**
     * 在方法的参数上可以直接使用 @NotBlank 等注解
     */
    @Override
    public void filedValidated(String username) {
        log.info("service username :{}", username);
    }

    /**
     * 不分组校验
     * 在方法上使用 @Valid 注解, 采用默认分组(实体也不指定分组)
     * 注意:接口中方法参数必须要加 @Valid 注解; 实现类中可加可不加
     *
     * @param paramGroupValidatedReq param
     * @return: void
     * Author: lzhch 2023/11/21 14:56
     * Since: 1.0.0
     */
    @Override
    public void create(@Valid ParamGroupValidatedReq paramGroupValidatedReq) {
        log.info("service result :{}", JSON.toJSONString(paramGroupValidatedReq));
    }

    /**
     * 分组校验
     * 在不分组校验的基础上对方法添加使用 @Validated 注解, 并指定分组
     * 注意:接口中方法参数必须要加 @Valid 注解; @Validated 可在接口中也可在实现类中; 不能只在实现类中添加两个注解
     *
     * @param paramGroupValidatedReq param
     * @return: void
     * Author: lzhch 2023/11/21 14:55
     * Since: 1.0.0
     */
    @Override
    @Validated(value = ParamGroupValidated.Create.class)
    public void create1(ParamGroupValidatedReq paramGroupValidatedReq) {
        log.info("service result :{}", JSON.toJSONString(paramGroupValidatedReq));
    }

}

项目地址

SpringBoot3-Practice/ParamGroupValidated at main · lzhcccccch/SpringBoot3-Practice (github.com)

相关推荐
曾令胜3 小时前
excel导出使用arthas动态追踪方法调用耗时后性能优化的过程
spring·性能优化·excel
.格子衫.3 小时前
Spring Boot 原理篇
java·spring boot·后端
多云几多4 小时前
Yudao单体项目 springboot Admin安全验证开启
java·spring boot·spring·springbootadmin
摇滚侠5 小时前
Spring Boot 3零基础教程,Spring Intializer,笔记05
spring boot·笔记·spring
Jabes.yang6 小时前
Java求职面试实战:从Spring Boot到微服务架构的技术探讨
java·数据库·spring boot·微服务·面试·消息队列·互联网大厂
兮动人6 小时前
Spring Bean耗时分析工具
java·后端·spring·bean耗时分析工具
MESSIR226 小时前
Spring IOC(控制反转)中常用注解
java·spring
摇滚侠6 小时前
Spring Boot 3零基础教程,Demo小结,笔记04
java·spring boot·笔记
华洛6 小时前
公开一个AI产品的商业逻辑与设计方案——AI带来的涂色卡自由
前端·后端·产品
追逐时光者7 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 57 期(2025年10.1-10.12)
后端·.net