惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
有赞技术团队
有赞技术团队
博客园 - 【当耐特】
云风的 BLOG
云风的 BLOG
Martin Fowler
Martin Fowler
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
量子位
Engineering at Meta
Engineering at Meta
博客园 - 叶小钗
T
The Blog of Author Tim Ferriss
Recent Announcements
Recent Announcements
罗磊的独立博客
B
Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
U
Unit 42
Microsoft Azure Blog
Microsoft Azure Blog
D
Docker
N
Netflix TechBlog - Medium
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
I
InfoQ
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
V2EX

博客园 - 码农张3

Windows下安装OpenSSL SpringBoot集成BCrypt密码加密库 Vue3 父组件引用子组件并控制子组件显隐及数据传递 MySQL创建用户且只能访问指定数据库表 Element Plus SCSS 变量覆盖用法 leaflet-canvasmarker添加的marker旋转问题 SpringBoot项目控制台打印sql设置 Windows 远程桌面复制粘贴突然无效 SQLServerException: 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。 右键用vscode打开文件夹 nodemon: 无法加载文件 C:\DevSoft\nodejs\node_global\nodemon.ps1 - 码农张3 EasyUI Datagrid添加右键导出菜单并导出数据 个人所得税App住房贷款利息解读 JavaScript学习笔记—DOM:操作class JavaScript学习笔记—DOM:通过属性读取样式 SQLServer数据库导出指定表里所有数据成insert语句 解决Mybatis xml文件中执行mysql语句时,语句后携带分号实现多语句执行报异常问题 JS去除字符串前面所有0 Redis启动服务报错:服务没有及时响应启动或者控制请求 JavaScript学习笔记—DOM:元素的添加、修改、删除 JavaScript学习笔记—全选、取消、反选、提交 JavaScript学习笔记—DOM:事件 JavaScript学习笔记—DOM:属性节点
自定义跨字段校验必填注解
码农张3 · 2026-04-15 · via 博客园 - 码农张3

应用场景:

  • 一个类中属性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 字段的值为“张”时,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;
}