当前位置: 首页 > news >正文

自定义跨字段校验必填注解

注解类

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 字段的值为“张”时,lastName 字段不能为空(null 或空串)。
@CrossFieldRequired(
dependField = "firstName",
targetField = "lastName",
expectedValue="张",
triggerType = CrossFieldRequired.TriggerType.NOT_EMPTY, //默认可不写
message = "姓氏不为空时,名字也不能为空")
@Data
public class UserAddForm {
@NotBlank(message = "姓氏不能为空")
private String firstName;
private String lastName;
}
http://www.cnnetsun.cn/news/3057786.html

相关文章:

  • AI 如何重塑 FMEA:从七步法向导到知识图谱,一个开源 QMS 的完整实践
  • 从“任意文件复制“深挖Java I/O:字符流与字节流的本质抉择
  • 中台建了、仓库搭了、报表做了,为什么业务还是要Excel?——从DAMA知识体系看数据中台治理落地的工程方法论
  • 奔驰STAR3 E/架构 高速视频链接(HSVL)
  • 专科大数据专业怎么专升本?升学路径+志愿规划+能力提升全攻略
  • XR 沉浸式娱乐在文旅行业的发展前景
  • FastAPI 项目架构设计:按技术分层还是按业务模块?
  • SOLIDWORKS中方程式的高级应用技巧有哪些?
  • langchain-langGraph 细节(面试)-持续补充
  • springCloud集成seata2.x
  • PG 日报|UUID 解析 SIMD 加速,AI 行业动态速览
  • MSPM0 I2C DMA传输配置详解:从FIFO触发到低功耗数据搬运
  • MinerU:开源多模态文档理解工具部署与实战指南
  • 我从顺丰转行学AI产品经理·扒完招聘数据没敢盲目乐观
  • 2026最新Power Settings Explore,解锁Windows隐藏电源神技
  • 豆包付费引发全民争议,深度分析通用AI VS 科研AI
  • AI 辅助调试:喂对信息,让 AI 做排除法
  • 开源Docker镜像安全审计实战:从漏洞扫描到权限最小化配置
  • 2026 年小程序开发公司推荐,靠谱服务商汇总
  • 【车载】轮速-AK协议:从电流信号到车辆控制的解码之旅
  • 内卷VS躺平VS转型:2026年程序员的第三条路
  • 从原理到实战:一文彻底吃透Transformer架构
  • 自己动手写一个spring之MVC_1
  • AI Skills技能系统,让 Agent 自动变强
  • 如何选择一家值得信赖的流水线贴标机供应商?
  • Android 模拟器开启关闭网络
  • SpaceX造富神话点燃资本热情,卫星通信产业在MWC26上海展现新图景
  • Wand-Enhancer:免费解锁游戏修改器完整功能的终极指南
  • 计算机毕业设计之基于深度学习的复杂场景下船舶目标检测
  • AutoCAD2027免费版下载安装教程(附安装包)AutoCAD 2027 保姆级安装教程