背景与痛点

在Spring Boot开发中,参数校验是不可或缺的一环。虽然Spring提供了@Valid、@NotNull、@Pattern等标准注解,但在实际业务中,我们经常遇到更复杂的校验需求:

相关类

自定义注解

import com.validators.CloseValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Desc 自定义注解
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {CloseValidator.class})// 指定校验器
public @interface ValidCloseForm {
    boolean required() default true;

    String message() default "Invalid parameters";

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

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

校验器

public class CloseValidator implements ConstraintValidator<ValidCloseForm, CloseDTO> {
    private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
	@Override
    public boolean isValid(CloseDTO formData, ConstraintValidatorContext context) {
        Set<ConstraintViolation<CloseDTO>> validate = validator.validate(formData, validFieldName);
        // 必填校验
        if (!validate.isEmpty()) {
            buildErrorMsg(context, validate.iterator().next().getMessage());
            return false;
        }
        // 自定义校验
        return validContains(formData, context);
    }

	private Boolean validContains(CloseDTO formData, ConstraintValidatorContext context) {
        
        String errorMsg="";
        // 根据业务自定义校验逻辑
        if (!CollectionUtils.isEmpty(fieldNameList)) {
            buildErrorMsg(context, String.join("、", fieldNameList) + ":字典值,未在指定范围");
            return false;
        }
        // 如果满足某个条件,入参=某个值.则分组校验groups=Condition.class
        if (入参==某个值) {
            Set<ConstraintViolation<CloseDTO>> validate = validator.validate(formData, Condition.class);
            // 必填校验
            if (!validate.isEmpty()) {
                buildErrorMsg(context, validate.iterator().next().getMessage());
                return false;
            }
        }

        return true;
    }

}
// 构建错误信息返回
  public void buildErrorMsg(ConstraintValidatorContext context, String validate) {
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(validate).addConstraintViolation();
    }

使用@ValidCloseForm

@Data
@AllArgsConstructor
@NoArgsConstructor
@ValidCloseForm// 类注解校验所有参数
public class CloseDTO {

    @NotBlank(message = "接口类型不能为空", groups = {Close.class, Judgment.class})
    private String interfaceType;

    @NotBlank(message = "单号不能为空", groups = {Close.class, Close.class})
    private String taskNo;

    @NotBlank(message = "case编码不能为空", groups = {Close.class})
    private String serviceCaseNo;

    private String isResponsible;

    @NotBlank(message = "节点不能为空", groups = {Judgment.class})
    private String node;

    @NotBlank(message = "一级分类编码不能为空", groups = {Condition.class})
    private String rewardPunishLevel1;
}

controller中使用

import org.springframework.validation.BindingResult;
import jakarta.validation.Valid;

    @PostMapping("/close")
    public R<Boolean> close(@RequestBody @Valid CloseDTO dto, BindingResult result) {
         if (result.hasErrors()) {
            List<ObjectError> allErrors = result.getAllErrors();
            ObjectError objectError = allErrors.get(0);
            // 此处可修改为自定义异常,方便全局链接
            throw new RuntimeException(objectError.getDefaultMessage());
        }
        // 业务处理
        return 结果;
    }

注意点

自定义注解指定校验器
@Constraint(validatedBy = {CloseValidator.class}) // 指定校验器

校验器中重写

 @Override
    public boolean isValid(CloseDTO formData, ConstraintValidatorContext context) {}

分组校验

private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

 Set<ConstraintViolation<CloseDTO>> validate = validator.validate(formData, validFieldName);
        // 必填校验
        if (!validate.isEmpty()) {
            buildErrorMsg(context, validate.iterator().next().getMessage());
         }

总结

通过自定义注解结合Validation,我们可以实现:
优势
代码整洁​ - 校验逻辑与业务逻辑分离
可复用​ - 注解一次定义,多处使用
可维护​ - 校验规则集中管理
可扩展​ - 轻松添加新的校验规则
标准化​ - 统一校验和错误处理
最佳实践建议
合理使用分组校验​ - 针对不同场景使用不同的校验规则
错误信息友好​ - 提供清晰明确的错误提示
组合注解​ - 简化常用校验组合
单元测试​ - 为每个校验器编写单元测试

日期检查:Date/Calendar

@Past 限定一个日期,日期必须是过去的日期
@Future 限定一个日期,日期必须是未来的日期
Hibernate Validator 附加的 constraint
@Email 被注释的属性必须是电子邮箱地址
@Length(min=,max=)被注释的字符串的大小必须在指定的范围内
@Range(min=,max=,message=) 被注释的属性必须在合适的范围内

数值检查:同时能验证一个字符串是否是满足限制的数字的字符串

@Min(value) 被注释的属性必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的属性必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的属性必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)被注释的属性必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的属性的大小必须在指定的范围内
@Digits(integer, fraction) 被注释的属性必须是一个数字,其值必须在可接受的范围内
@Pattern(regex=,flag=) 被注释的属性必须符合指定的正则表达式

通用

@Null 被注释的属性必须为 null
@NotNull被注释的属性必须不为 null
@AssertTrue 被注释的属性必须为 true
@AssertFalse 被注释的属性必须为 false
字符串/数组/集合检查:(字符串本身就是个数组)
@Pattern(regexp=“reg”) 验证字符串满足正则
@Size(max, min) 验证字符串、数组、集合长度范围
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐