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

📅 2026/6/30 4:41:10
自定义跨字段校验必填注解
注解类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 跨字段校验必填注解*/DocumentedConstraint(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 ConstraintValidatorCrossFieldRequired, Object {// 被依赖的字段private String dependField;// 被依赖字段的期望值private String expectedValue;// 目标字段private String targetField;// 触发方式private CrossFieldRequired.TriggerType triggerType;Overridepublic void initialize(CrossFieldRequired constraintAnnotation) {this.dependField constraintAnnotation.dependField();this.expectedValue constraintAnnotation.expectedValue();this.targetField constraintAnnotation.targetField();this.triggerType constraintAnnotation.triggerType();}Overridepublic 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 姓氏不为空时名字也不能为空)Datapublic class UserAddForm {NotBlank(message 姓氏不能为空)private String firstName;private String lastName;}