问题再现
交易系统对应不同类型的订单请求,公共字段是放在了一个父类里,例如:
@Setter
@Getter
public abstract class BaseOrderRequest{
@NotNull
@Min(1)
@Max(1000000)
private Long totalAmount;
......
}
然后不同类型的下单请求再去继承这个父类,例如微信下单请求:
public class WeiXinOrderRequest extends BaseOrderRequest{
private String openId;
private String subOpenId;
......
}
按理说这样是很符合要求的,但是存在一个问题,我们将公共字段抽取到了基类里面,对应订单金额的校验也同样放在了基类里面。这样不同类型的交易无法做更精细的校验,例如我们现在微信下单需要将金额由100W扩展到200W,你怎么办?
可能的方案
想到的方案有两种:
1-子类也添加这个字段,通过覆盖父类同名字段,例如:
public class WeiXinOrderRequest extends BaseOrderRequest{
private String openId;
private String subOpenId;
@NotNull
@Min(1)
@Max(2000000)
private Long totalAmount;
......
}
2-子类覆写getter方法,把校验加载getter方法上面,例如:
public class WeiXinOrderRequest extends BaseOrderRequest{
private String openId;
private String subOpenId;
@NotNull
@Min(1)
@Max(2000000)
publict Long getTotalAmount(){
return super.getTotalAmount();
};
......
}
本以为在Hibernate Validator校验框架下,如果覆盖了就只会校验子类的字段。但是不幸的是在给定150W的时候,第一种方案出现了金额不能为空的错误提示,第二种仍然是提示最大金额为100W的提示。两种错误的本质,都是父类的相关字段仍然被校验了。(可以试着据此去解释为什么第一种方案会报金额不能为空的错误提示)
最后还是不得不翻看下Hibernate Validator的源码,发现在校验的时候,会去获取被校验对象的类继承关系(类和接口):
然后循环的去对所有类对象的注解进行校验:
while(var5.hasNext()) {
Class<? super U> clazz = (Class)var5.next();
BeanMetaData<? super U> hostingBeanMetaData = this.beanMetaDataManager.getBeanMetaData(clazz);
......
Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints();
this.validateConstraintsForSingleDefaultGroupElement(validationContext, valueContext, validatedInterfaces, clazz, metaConstraints, Group.DEFAULT_GROUP);
......
}
到此其实也就真相大白了,所有在子类里耍的小聪明最后都没有用,父类上的字段仍然是会校验的。
解决方案
我们再回到最初的问题,既然在不同的场景下,对公共字段的限制是不一样的,那么我们的校验注解就不应该放到基类里面去,而是应该放在子类的getter里面,例如:
@Setter
@Getter
public abstract class BaseOrderRequest{
@NotNull
@Min(1)
private Long totalAmount;
......
}
@Getter
@Setter
public class WeiXinOrderRequest extends BaseOrderRequest{
private String openId;
private String subOpenId;
@Max(2000000)
public Long getTotalAmount(){
return super.getTotalAmount();
}
......
}
这种方案也不是十全十美,有个缺点,就是默认的金额校验没了,每个子类都需要去更改。当然对于缩小限制类型的,则完全没有这个顾虑,例如:金额只能是10W。即使父类校验通过了,子类也不会通过。通过这个案例,我们了解到在设计校验的时候,业务上要尽早的考虑到变化,将可能变化的部分交给子类去限制,避免后期出现校验规则变化导致大改的情况。