使用JSR-303和Spring的Validator的组合为spring引导端点实现自定义validation逻辑
我正在尝试使用JSR-303 Bean Validation API
和Spring's Validator
的组合为Spring引导端点实现一些自定义validation逻辑。
基于Validator类图,似乎可以扩展CustomValidatorBean
, SpringValidatorAdapter
或LocalValidatorFactoryBean
以将一些自定义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" }
我希望这有帮助。
- JSR303自定义validation器被调用两次
- java / beans validation – collection / map不包含空值
- Resteasy Beanvalidation未被调用
- Autowired Repository在Custom Constraint Validator中为空
- 使javaxvalidation错误消息更具体
- 使用Java EE 6 Beanvalidation
- 自动级联bean-validation递归validation,@ Valid注释
- Java Bean Validation(JSR303)约束涉及几个bean属性之间的关系
- 如何在ConstraintValidator中自动assembly服务