韩琳 發表於 2026-4-15 14:29:00

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

<blockquote>
<p>应用场景:</p>
<ul>
<li>一个类中属性a不为空时,属性b不能为空</li>
<li>一个类中属性a不为xxx时,属性b不能为空</li>
<li>一个类中属性a为xxx时,属性b不能为空</li>
</ul>
</blockquote>
<h2 id="注解类">注解类</h2>
<pre><code class="language-java">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&lt;?&gt;[] groups() default {};

    Class&lt;? extends Payload&gt;[] payload() default {};

    String dependField();

    String expectedValue();

    String targetField();
   
    // 触发方式, 默认非空触发
    TriggerType triggerType() default TriggerType.NOT_EMPTY;

    enum TriggerType {
      NOT_EMPTY,
      NOT_EQUALS,
      EQUALS
    }
}

</code></pre>
<h2 id="注解验证器类">注解验证器类</h2>
<pre><code class="language-java">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&lt;CrossFieldRequired, Object&gt; {
    // 被依赖的字段
    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 &amp;&amp; !dependValue.toString().trim().isEmpty();
            } else if (triggerType == CrossFieldRequired.TriggerType.NOT_EQUALS) {
                shouldValidate = dependValue != null &amp;&amp; !expectedValue.equals(dependValue.toString());
            } else if (triggerType == CrossFieldRequired.TriggerType.EQUALS) {
                shouldValidate = dependValue != null &amp;&amp; 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&lt;?&gt; clazz, String fieldName) {
      while (clazz != null) {
            try {
                return clazz.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
      }
      return null;
    }
}

</code></pre>
<h2 id="使用示例">使用示例</h2>
<pre><code class="language-java">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;
}

</code></pre><br><br>
来源:https://www.cnblogs.com/zibocoder/p/19871453
頁: [1]
查看完整版本: 自定义跨字段校验必填注解