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

推荐订阅源

Simon Willison's Weblog
Simon Willison's Weblog
P
Privacy International News Feed
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Troy Hunt's Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Attack and Defense Labs
Attack and Defense Labs
S
Secure Thoughts
V2EX - 技术
V2EX - 技术
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
O
OpenAI News
Cloudbric
Cloudbric
Google Online Security Blog
Google Online Security Blog
Schneier on Security
Schneier on Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Help Net Security
Help Net Security
Cyberwarzone
Cyberwarzone
G
GRAHAM CLULEY
L
Lohrmann on Cybersecurity
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Spread Privacy
Spread Privacy
NISL@THU
NISL@THU
N
News and Events Feed by Topic
T
Tenable Blog
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
The Hacker News
The Hacker News
C
CXSECURITY Database RSS Feed - CXSecurity.com
宝玉的分享
宝玉的分享
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
V
Visual Studio Blog
P
Proofpoint News Feed
Webroot Blog
Webroot Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 三生石上(FineUI控件)
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Jina AI
Jina AI
雷峰网
雷峰网
T
The Blog of Author Tim Ferriss
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
L
LangChain Blog
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东

博客园 - 码农张3

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;
}