Spring Boot自定义注解 + Validation实现优雅的参数校验
*** Desc 自定义注解*/@Constraint(validatedBy = {CloseValidator.class})// 指定校验器Class<?Class<?通过自定义注解结合Validation,我们可以实现:优势代码整洁 - 校验逻辑与业务逻辑分离可复用 - 注解一次定义,多处使用可维护 - 校验规则集中管理可扩展 - 轻松添加新的校验规则标准化 - 统一校验和错误处
Spring Boot自定义注解 + Validation实现优雅的参数校验
背景与痛点
在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只应用于字符串且在比较时会去除字符串的空格
更多推荐


所有评论(0)