使用JSR-303和Spring的Validator的组合为spring引导端点实现自定义validation逻辑

我正在尝试使用JSR-303 Bean Validation APISpring's Validator的组合为Spring引导端点实现一些自定义validation逻辑。

基于Validator类图,似乎可以扩展CustomValidatorBeanSpringValidatorAdapterLocalValidatorFactoryBean以将一些自定义validation逻辑添加到重写方法validate(Object target, Errors errors)

验证器类图

但是,如果我创建一个validation器来扩展这三个类中的任何一个并使用@InitBinder注册它validate(Object target, Errors errors)则永远不会调用其validate(Object target, Errors errors)方法,也不会执行validation。 如果我删除@InitBinder那么默认的springvalidation器会执行JSR-303 Bean Validation

rest控制器:

 @RestController public class PersonEndpoint { @InitBinder("person") protected void initBinder(WebDataBinder binder) { binder.setValidator(new PersonValidator()); } @RequestMapping(path = "/person", method = RequestMethod.PUT) public ResponseEntity add(@Valid @RequestBody Person person) { person = personService.save(person); return ResponseEntity.ok().body(person); } } 

自定义validation器:

 public class PersonValidator extends CustomValidatorBean { @Override public boolean supports(Class clazz) { return Person.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { super.validate(target, errors); System.out.println("PersonValidator.validate() target="+ target +" errors="+ errors); } } 

如果我的validation器实现了org.springframework.validation.Validator validate(Object target, Errors errors)则会调用其validate(Object target, Errors errors)方法,但不会在它之前执行JSR-303 Bean Validation 。 我可以实现我的自定义JSR-303validation,类似于SpringValidatorAdapter实现其JSR-303 Bean Validation的方式,但必须有一种方法来扩展它:

  @Override public void validate(Object target, Errors errors) { if (this.targetValidator != null) { processConstraintViolations(this.targetValidator.validate(target), errors); } } 

我已经看过使用自定义JSR-303约束来避免一起使用org.springframework.validation.Validator但是必须有一种方法来使自定义validation器工作。

结合这两者,Spring validation文档不是很清楚:

应用程序还可以为每个DataBinder实例注册其他Spring Validator实例,如第9.8.3节“配置DataBinder”中所述。 这可以用于在不使用注释的情况下插入validation逻辑。

然后,它涉及配置多个Validator实例

还可以通过dataBinder.addValidators和dataBinder.replaceValidators为DataBinder配置多个Validator实例。 将全局配置的Beanvalidation与在DataBinder实例上本地配置的Springvalidation器组合时,这非常有用。 见???。

我正在使用spring boot 1.4.0。

Per @ M.Deinum – 使用addValidators()而不是setValidator()就可以了。 我也同意使用JSR-303,@ AssertTrue基于方法的注释专门用于交叉字段validation,可能是一个更清洁的解决方案。 代码示例可从https://github.com/pavelfomin/spring-boot-rest-example/tree/feature/custom-validator获得 。 在该示例中,中间名称validation通过自定义弹簧validation器执行,而姓氏validation由默认的jsr 303validation器处理。

扩展LocalValidatorFactoryBean可以解决这个问题,你可以覆盖这个类中的validate方法,给出你想要的任何行为。

在我的情况下,我需要在同一个Controller中的不同方法中对同一模型使用JSR-303 AND自定义validation器,通常建议使用@InitBinder,但这对我的情况来说还不够,因为InitBinder在Model和Validator之间进行绑定(如果你使用@RequestBody InitBinder只适用于一个模型和每个控制器一个validation器)。

调节器

 @RestController public class LoginController { @PostMapping("/test") public Test test(@Validated(TestValidator.class) @RequestBody Test test) { return test; } @PostMapping("/test2") public Test test2(@Validated @RequestBody Test test) { return test; } } 

自定义validation器

 public class TestValidator implements org.springframework.validation.Validator { @Override public boolean supports(Class clazz) { return Test.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { Test test = (Test) target; errors.rejectValue("field3", "weird"); System.out.println(test.getField1()); System.out.println(test.getField2()); System.out.println(test.getField3()); } } 

要validation的类

 public class Test { @Size(min = 3) private String field2; @NotNull @NotEmpty private String field1; @NotNull @Past private LocalDateTime field3; //... //getter/setter //... } 

CustomLocalValidatorFactoryBean

 public class CustomLocalValidatorFactoryBean extends LocalValidatorFactoryBean { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void validate(@Nullable Object target, Errors errors, @Nullable Object... validationHints) { Set concreteValidators = new LinkedHashSet<>(); Set> interfaceGroups = new LinkedHashSet<>(); extractConcreteValidatorsAndInterfaceGroups(concreteValidators, interfaceGroups, validationHints); proccessConcreteValidators(target, errors, concreteValidators); processConstraintViolations(super.validate(target, interfaceGroups.toArray(new Class[interfaceGroups.size()])), errors); } private void proccessConcreteValidators(Object target, Errors errors, Set concreteValidators) { for (Validator validator : concreteValidators) { validator.validate(target, errors); } } private void extractConcreteValidatorsAndInterfaceGroups(Set concreteValidators, Set> groups, Object... validationHints) { if (validationHints != null) { for (Object hint : validationHints) { if (hint instanceof Class) { if (((Class) hint).isInterface()) { groups.add((Class) hint); } else { Optional validatorOptional = getValidatorFromGenericClass(hint); if (validatorOptional.isPresent()) { concreteValidators.add(validatorOptional.get()); } } } } } } @SuppressWarnings("unchecked") private Optional getValidatorFromGenericClass(Object hint) { try { Class clazz = (Class) Class.forName(((Class) hint).getName()); return Optional.of(clazz.newInstance()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { logger.info("There is a problem with the class that you passed to " + " @Validated annotation in the controller, we tried to " + " cast to org.springframework.validation.Validator and we cant do this"); } return Optional.empty(); } } 

配置应用程序

 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public javax.validation.Validator localValidatorFactoryBean() { return new CustomLocalValidatorFactoryBean(); } } 

输入到/test端点:

 { "field1": "", "field2": "aaaa", "field3": "2018-04-15T15:10:24" } 

来自/test endpoint的输出:

 { "timestamp": "2018-04-16T17:34:28.532+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "weird.test.field3", "weird.field3", "weird.java.time.LocalDateTime", "weird" ], "arguments": null, "defaultMessage": null, "objectName": "test", "field": "field3", "rejectedValue": "2018-04-15T15:10:24", "bindingFailure": false, "code": "weird" }, { "codes": [ "NotEmpty.test.field1", "NotEmpty.field1", "NotEmpty.java.lang.String", "NotEmpty" ], "arguments": [ { "codes": [ "test.field1", "field1" ], "arguments": null, "defaultMessage": "field1", "code": "field1" } ], "defaultMessage": "Não pode estar vazio", "objectName": "test", "field": "field1", "rejectedValue": "", "bindingFailure": false, "code": "NotEmpty" } ], "message": "Validation failed for object='test'. Error count: 2", "path": "/user/test" } 

输入到/test2端点:

 { "field1": "", "field2": "aaaa", "field3": "2018-04-15T15:10:24" } 

输出到/test2端点:

 { "timestamp": "2018-04-16T17:37:30.889+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "NotEmpty.test.field1", "NotEmpty.field1", "NotEmpty.java.lang.String", "NotEmpty" ], "arguments": [ { "codes": [ "test.field1", "field1" ], "arguments": null, "defaultMessage": "field1", "code": "field1" } ], "defaultMessage": "Não pode estar vazio", "objectName": "test", "field": "field1", "rejectedValue": "", "bindingFailure": false, "code": "NotEmpty" } ], "message": "Validation failed for object='test'. Error count: 1", "path": "/user/test2" } 

我希望这有帮助。