首页 > 基础资料 博客日记
自定义跨字段校验必填注解
2026-04-15 14:30:02基础资料围观2次
极客资料网推荐自定义跨字段校验必填注解这篇文章给大家,欢迎收藏极客资料网享受知识的乐趣
应用场景:
- 一个类中属性a不为空时,属性b不能为空
- 一个类中属性a不为xxx时,属性b不能为空
- 一个类中属性a为xxx时,属性b不能为空
注解类
package com.xxx.common.core.annotation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* @Author zibocoder
* @Date 2026/04/13
* @Description 跨字段校验必填注解
*/
@Documented
@Constraint(validatedBy = CrossFieldRequiredValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CrossFieldRequired {
String message() default "该字段为必填项";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String dependField();
String expectedValue();
String targetField();
// 触发方式, 默认非空触发
TriggerType triggerType() default TriggerType.NOT_EMPTY;
enum TriggerType {
NOT_EMPTY,
NOT_EQUALS,
EQUALS
}
}
注解验证器类
package com.xxx.common.core.annotation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;
/**
* @Author zibocoder
* @Date 2026/04/13
* @Description 跨字段校验必填注解验证器
*/
public class CrossFieldRequiredValidator implements ConstraintValidator<CrossFieldRequired, Object> {
// 被依赖的字段
private String dependField;
// 被依赖字段的期望值
private String expectedValue;
// 目标字段
private String targetField;
// 触发方式
private CrossFieldRequired.TriggerType triggerType;
@Override
public void initialize(CrossFieldRequired constraintAnnotation) {
this.dependField = constraintAnnotation.dependField();
this.expectedValue = constraintAnnotation.expectedValue();
this.targetField = constraintAnnotation.targetField();
this.triggerType = constraintAnnotation.triggerType();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
try {
Field dependFld = getField(value.getClass(), dependField);
if (dependFld == null) {
return true;
}
dependFld.setAccessible(true);
Object dependValue = dependFld.get(value);
// 是否应该校验
boolean shouldValidate = false;
if (triggerType == CrossFieldRequired.TriggerType.NOT_EMPTY) {
shouldValidate = dependValue != null && !dependValue.toString().trim().isEmpty();
} else if (triggerType == CrossFieldRequired.TriggerType.NOT_EQUALS) {
shouldValidate = dependValue != null && !expectedValue.equals(dependValue.toString());
} else if (triggerType == CrossFieldRequired.TriggerType.EQUALS) {
shouldValidate = dependValue != null && expectedValue.equals(dependValue.toString());
}
if (shouldValidate) {
Field targetFld = getField(value.getClass(), targetField);
if (targetFld == null) {
return true;
}
targetFld.setAccessible(true);
Object targetValue = targetFld.get(value);
if (isEmpty(targetValue)) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode(targetField)
.addConstraintViolation();
return false;
}
}
return true;
} catch (Exception e) {
return true;
}
}
/**
* 判断对象是否为空
*
* @param value 对象
* @return true:为空 false:不为空
*/
private boolean isEmpty(Object value) {
if (value == null) {
return true;
}
if (value instanceof String) {
return ((String) value).trim().isEmpty();
}
return false;
}
/**
* 递归查找字段,支持继承场景
*
* @param clazz 类
* @param fieldName 字段名
* @return 字段
*/
private Field getField(Class<?> clazz, String fieldName) {
while (clazz != null) {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
}
使用示例
package com.xxx.domain.form;
import com.xxx.common.core.annotation.CrossFieldRequired;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @Author zibocoder
* @Date 2026/04/13
* @Description
*/
// 类级别的自定义验证注解,实现跨字段条件校验:当 firstName 字段的值不等于 "现金加油" 时,unitPrice 字段不能为空(null 或空串)。用于确保非现金加油场景下单价为必填项。
@CrossFieldRequired(
dependField = "firstName",
targetField = "lastName",
triggerType = CrossFieldRequired.TriggerType.NOT_EMPTY, //默认可不写
message = "姓氏不为空时,名字也不能为空")
@Data
public class UserAddForm {
@NotBlank(message = "姓氏不能为空")
private String firstName;
private String lastName;
}
文章来源:https://www.cnblogs.com/zibocoder/p/19871453
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:
相关文章
最新发布
- FastAPI配置管理避坑指南:从硬编码到 .env 与 pydantic_settings 类,连路由用法都给你捋清楚
- 深入 Open Agent SDK(四):多 Agent 协作——子代理、团队与任务编排
- ArrayPoolWrapper简洁、安全的ArrayPool
- claude-code 学习手册(心得):
- # 【拾零】0 - 开箱即用的现代风终端 |Ghostty + Fish + Starship + fzf + zoxide + Raycast
- Redis--Set、ZSet操作命令和benchmark测试工具
- 异源数据同步 → 记一次 DataX 已同步数据量优化
- SourceGenerator之扑风捉影
- 【译】在 Visual Studio 中完全掌控您的悬浮窗口
- 当 CGO 遇见 Zig:一种更优雅的折腾方式,对比 GCC 后端

