Springboot参数分组校验
文章目录
- 
- Springboot参数分组校验
 - 
- 
- 简介
 - 代码准备
 - 单个或多个参数的校验
 - [非 JSON 格式的对象参数校验](#非 JSON 格式的对象参数校验)
 - [JSON 格式的对象参数校验](#JSON 格式的对象参数校验)
 - [Service 层校验](#Service 层校验)
 - 项目地址
 
 
 - 
 
 
简介
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)