如何使用Hibernate-Validator验证接口参数?优化Order类校验方案
问题描述
现有如下接口入参校验代码,希望改用Hibernate-Validator实现:
原始实体类
public class Order { private String orderNo; private String orderId; private String status; private String startTime; private String endTime; //getter and setter... }
原始校验逻辑
public class OrderService { public Object search(Order order) throws Exception { String message = ""; if (order.getOrderId().isEmpty() && order.getOrderNo().isEmpty() && order.getStatus().isEmpty()) { if (order.getStartTime().isEmpty() && order.getEndTime().isEmpty()) message = "xxx"; } if (!message.isEmpty()) throw new Exception(message); Object result = null; // 根据order属性拼接SQL并查询结果 // result = sql query result return result; } }
尝试用Hibernate-Validator的分组机制实现,但参数较多时需要编写大量分组类,显得繁琐。目前有100多个接口且后续会新增,想问使用类级约束是否是好选择? 另外还有没有其他方式可以验证search方法中的Order参数?
尝试的分组实现代码
实体类(分组注解)
public class Order { @Empty(groups = One.class) @NotEmpty(groups = Two.class) private String orderNo; @Empty(groups = One.class) @NotEmpty(groups = Three.class) private String orderId; @Empty(groups = One.class) @NotEmpty(groups = Four.class) private String status; @NotEmpty(groups = One.class) private String startTime; @NotEmpty(groups = One.class) private String endTime; }
校验工具类
public class BeanValidatorUtils { static Validator validator; static { HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class).configure(); ValidatorFactory factory = configuration.failFast(true).buildValidatorFactory(); validator = factory.getValidator(); } public static <T> void validation(T beanParam) throws AppException { if (!containsGroup(beanParam, One.class)) return; Set<ConstraintViolation<T>> validate = validator.validate(beanParam, One.class); ConstraintViolation<T> constraintViolation = validate.iterator().next(); String firstViolationMessage = constraintViolation.getMessage(); if (!validate.isEmpty() && containsGroup(beanParam, Two.class)) { validate = validator.validate(beanParam, Two.class); } if (!validate.isEmpty() && containsGroup(beanParam, Three.class)) { validate = validator.validate(beanParam, Three.class); } if (!validate.isEmpty()) throw new AppException(firstViolationMessage); } private static boolean containsGroup(Object bean, Class<?> groupClazz) { // 判断bean是否包含指定分组的逻辑 } }
解决方案
针对你的场景,这里提供几种更简洁的实现方式:
1. 类级约束(推荐,适配复杂组合校验)
类级约束是最匹配你当前校验逻辑的方案,因为你的规则是多属性间的组合判断,而非单一属性的独立校验。这种方式不需要大量分组类,直接在实体类上标记自定义约束即可,适合100+接口的场景,每个接口的实体类可独立维护自身的类级约束,扩展性强。
实现步骤:
1.1 自定义类级约束注解
@Target({TYPE}) @Retention(RUNTIME) @Constraint(validatedBy = OrderSearchValidator.class) public @interface ValidOrderSearch { String message() default "xxx"; // 自定义错误提示 Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
1.2 实现约束校验器
public class OrderSearchValidator implements ConstraintValidator<ValidOrderSearch, Order> { @Override public boolean isValid(Order order, ConstraintValidatorContext context) { // 空值判断,避免NPE boolean orderIdEmpty = order.getOrderId() == null || order.getOrderId().isEmpty(); boolean orderNoEmpty = order.getOrderNo() == null || order.getOrderNo().isEmpty(); boolean statusEmpty = order.getStatus() == null || order.getStatus().isEmpty(); boolean startTimeEmpty = order.getStartTime() == null || order.getStartTime().isEmpty(); boolean endTimeEmpty = order.getEndTime() == null || order.getEndTime().isEmpty(); // 匹配原始校验逻辑:当orderId、orderNo、status全为空时,startTime和endTime不能同时为空 if (orderIdEmpty && orderNoEmpty && statusEmpty) { return !(startTimeEmpty && endTimeEmpty); } // 其他场景均通过校验 return true; } }
1.3 标记实体类
@ValidOrderSearch public class Order { private String orderNo; private String orderId; private String status; private String startTime; private String endTime; //getter and setter... }
1.4 调用校验
在OrderService中直接触发校验:
public class OrderService { private Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); public Object search(Order order) throws Exception { Set<ConstraintViolation<Order>> violations = validator.validate(order); if (!violations.isEmpty()) { throw new Exception(violations.iterator().next().getMessage()); } // 后续SQL拼接及查询逻辑 Object result = null; // result = sql query result return result; } }
优势:
- 校验逻辑集中直观,便于维护
- 无需编写大量分组类,每个实体类的约束独立可控
- 适配复杂组合校验场景,扩展性好
2. 编程式校验(灵活适配临时场景)
如果不想编写自定义注解,可直接在服务层使用Hibernate-Validator的编程式API实现校验,无需修改实体类:
public class OrderService { private Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); public Object search(Order order) throws Exception { // 空值判断 boolean orderIdEmpty = order.getOrderId() == null || order.getOrderId().isEmpty(); boolean orderNoEmpty = order.getOrderNo() == null || order.getOrderNo().isEmpty(); boolean statusEmpty = order.getStatus() == null || order.getStatus().isEmpty(); boolean startTimeEmpty = order.getStartTime() == null || order.getStartTime().isEmpty(); boolean endTimeEmpty = order.getEndTime() == null || order.getEndTime().isEmpty(); // 触发校验规则 if (orderIdEmpty && orderNoEmpty && statusEmpty && startTimeEmpty && endTimeEmpty) { throw new Exception("xxx"); } // 后续业务逻辑 Object result = null; // result = sql query result return result; } }
优势:快速实现,无需额外注解和校验器类;劣势:校验逻辑与业务代码耦合,100+接口场景下代码重复率高。
3. 优化分组机制(减少分组类数量)
如果必须使用分组,可通过接口继承复用基础规则,减少分组类定义:
// 基础分组 public interface SearchBaseGroup {} // 子分组,继承基础分组规则 public interface SearchWithOrderNoGroup extends SearchBaseGroup {} public interface SearchWithOrderIdGroup extends SearchBaseGroup {}
实体类中使用分组:
public class Order { @NotEmpty(groups = SearchWithOrderNoGroup.class) private String orderNo; @NotEmpty(groups = SearchWithOrderIdGroup.class) private String orderId; @NotEmpty(groups = SearchWithStatusGroup.class) private String status; @NotEmpty(groups = SearchBaseGroup.class) private String startTime; @NotEmpty(groups = SearchBaseGroup.class) private String endTime; }
校验时根据场景选择对应分组,这种方式虽减少了部分重复定义,但复杂度仍高于类级约束。
总结
针对你的场景(100+接口、复杂组合校验),类级约束是最优选择,它既保持了校验逻辑的独立性,又避免了分组类的繁琐,维护性和扩展性俱佳。编程式校验适合临时简单场景,优化后的分组机制适合依赖分组的已有架构,但整体复杂度更高。
内容的提问来源于stack exchange,提问作者Ulimp
相关产品推荐
相关产品推荐

