应采用策略模式+模板方法实现多态校验:统一Validator接口与注册中心,子类校验器显式委托父类校验,结合JSR-303注解与自定义约束处理联合规则,Spring中分层协作确保语义清晰、可扩展。

在 Java 多态继承体系中,父类与子类属性的联合参数校验不能依赖简单的 if 判断或各自独立校验,而应借助面向对象设计原则和校验框架能力,实现**统一入口、分层校验、语义清晰、可扩展**的效果。核心思路是:校验逻辑随对象类型动态委派,同时保证父类约束不被绕过,子类新增约束能自然叠加。
用 Validator 接口 + 策略模式解耦校验逻辑
避免在每个实体类里硬编码校验逻辑,定义统一校验契约:
示例:
public interface Validator<t> {
ValidationResult validate(T obj);
}
// 全局注册中心(可用 Spring Bean 或 Map 维护)
public class ValidatorRegistry {
private static final Map<class>, Validator>> registry = new HashMap();
public static <t> void register(Class<t> type, Validator<t> validator) {
registry.put(type, validator);
}
@SuppressWarnings("unchecked")
public static <t> ValidationResult validate(T obj) {
Validator<t> validator = (Validator<t>) registry.get(obj.getClass());
return validator != null ? validator.validate(obj) : ValidationResult.success();
}
}</t></t></t></t></t></t></class></t>
为父类和每个子类分别实现 Validator,子类校验器显式调用父类校验器:
- 父类校验器只校验父类字段(如
id,status) - 子类校验器先调用
superValidator.validate(superObj),再校验自身字段(如discountRate,expiryDate) - 校验失败时合并错误信息,保持上下文完整
结合 JSR-303(Jakarta Bean Validation)做声明式基础校验
利用注解对字段做静态约束(如 @NotNull, @Min(0)),但需注意多态场景下的限制:
- 父类字段注解自动生效于所有子类实例(JVM 反射可读取继承字段)
- 子类新增字段的注解也正常生效
- 但跨字段联合校验(如“若
type == 'COUPON',则discountRate必须 > 0.1”)无法仅靠标准注解完成,需自定义@Constraint
自定义约束示例(校验父+子字段组合):
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CouponRuleValidator.class)
public @interface ValidCouponRule {
String message() default "Coupon rule violation";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
// 校验器中可安全转型并访问父子字段
public class CouponRuleValidator implements ConstraintValidator<validcouponrule coupon> {
@Override
public boolean isValid(Coupon coupon, ConstraintValidatorContext context) {
if (coupon == null) return true;
// 访问父类 status 和子类 discountRate
return !"DRAFT".equals(coupon.getStatus()) || coupon.getDiscountRate() > 0.1;
}
}</validcouponrule>
运行时类型识别 + 模板方法统一入口
若不用外部框架,可在基类中定义模板方法,强制子类参与校验流程:
public abstract class BaseEntity {
protected String id;
protected String status;
// 模板方法:固定执行顺序
public final ValidationResult validate() {
ValidationResult result = validateSelf(); // 基础字段校验(可由子类重写)
if (!result.isValid()) return result;
ValidationResult extended = validateExtended(); // 钩子方法,子类必须实现
return result.merge(extended); // 合并结果
}
protected ValidationResult validateSelf() {
if (StringUtils.isBlank(id)) {
return ValidationResult.error("id is required");
}
if (!List.of("ACTIVE", "INACTIVE").contains(status)) {
return ValidationResult.error("invalid status");
}
return ValidationResult.success();
}
protected abstract ValidationResult validateExtended();
}
// 子类只需专注自身逻辑,不重复父类校验
public class Coupon extends BaseEntity {
private BigDecimal discountRate;
private LocalDate expiryDate;
@Override
protected ValidationResult validateExtended() {
if (discountRate != null && discountRate.compareTo(BigDecimal.ZERO) = 0");
}
if (expiryDate != null && expiryDate.isBefore(LocalDate.now())) {
return ValidationResult.error("expiryDate cannot be in the past");
}
return ValidationResult.success();
}
}
Spring 场景下推荐组合方案
若项目已用 Spring,建议三层协作:
- Controller 层:用
@Valid触发基础注解校验(含继承字段) - Service 层:调用
ValidatorRegistry.validate(entity)执行业务级联合校验(含状态流转、跨字段规则等) - 校验失败统一抛出
IllegalArgumentException或自定义ValidationException,由全局异常处理器转为 HTTP 400 响应
这样既复用标准生态,又保留业务灵活性,避免校验逻辑散落在各 service 方法中。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











