有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java使用JSR303和spring的验证器的组合为spring引导端点实现自定义验证逻辑

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

根据验证器类图,似乎可以扩展CustomValidatorBeanSpringValidatorAdapterLocalValidatorFactoryBean中的一个,将一些自定义验证逻辑添加到重写的方法validate(Object target, Errors errors)

Validator class diagram

然而,如果我创建一个扩展这三个类中任何一个的验证器,并使用@InitBinder注册它,它的validate(Object target, Errors errors)方法永远不会被调用,也不会执行任何验证。如果我删除@InitBinder,那么默认的spring验证器将执行JSR-303 Bean Validation

休息控制器:

@RestController
public class PersonEndpoint {

    @InitBinder("person")
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(new PersonValidator());
    }

    @RequestMapping(path = "/person", method = RequestMethod.PUT)
    public ResponseEntity<Person> add(@Valid @RequestBody Person person) {

        person = personService.save(person);
        return ResponseEntity.ok().body(person);
    }
}

自定义验证程序:

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);
    }

}

如果我的验证器实现了org.springframework.validation.Validator,那么它的validate(Object target, Errors errors)方法会被调用,但JSR-303 Bean Validation不会在它之前执行。我可以实现自定义JSR-303验证,类似于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,但必须有一种方法使自定义验证器工作

Spring validation documentation在结合这两个方面并不十分明确:

An application can also register additional Spring Validator instances per DataBinder instance, as described in Section 9.8.3, “Configuring a DataBinder”. This may be useful for plugging in validation logic without the use of annotations.

然后在后面,它涉及到配置多个验证器实例

A DataBinder can also be configured with multiple Validator instances via dataBinder.addValidators and dataBinder.replaceValidators. This is useful when combining globally configured Bean Validation with a Spring Validator configured locally on a DataBinder instance. See ???.

我使用的是spring boot 1.4.0


共 (1) 个答案

  1. # 1 楼答案

    这个问题可以通过扩展LocalValidatoryFactoryBean来解决,您可以重写这个类中的validate方法,给出您想要的任何行为

    在我的例子中,我需要在同一个控制器的不同方法中使用JSR-303和同一模型的自定义验证器,通常建议使用@InitBinder,但这对于我的案例来说还不够,因为InitBinder在模型和验证器之间进行绑定(如果您使用@RequestBody InitBinder,那么它只针对一个模型和每个控制器一个验证器)

    控制器

    @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;
        }
    }
    

    自定义验证器

    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());
         }
    }
    

    类进行验证

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

    CustomLocalValidatoryBean

    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<Validator> concreteValidators = new LinkedHashSet<>();
            Set<Class<?>> 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<Validator> concreteValidators) {
            for (Validator validator : concreteValidators) {
                validator.validate(target, errors);
            }
        }
    
        private void extractConcreteValidatorsAndInterfaceGroups(Set<Validator> concreteValidators, Set<Class<?>> groups, Object... validationHints) {
            if (validationHints != null) {
                for (Object hint : validationHints) {
                    if (hint instanceof Class) {
                        if (((Class<?>) hint).isInterface()) {
                            groups.add((Class<?>) hint);
                        } else {
                            Optional<Validator> validatorOptional = getValidatorFromGenericClass(hint);
                            if (validatorOptional.isPresent()) {
                                concreteValidators.add(validatorOptional.get());
                            }
                        }
                    }
                }
            }
        }
    
        @SuppressWarnings("unchecked")
        private Optional<Validator> getValidatorFromGenericClass(Object hint) {
            try {
                Class<Validator> clazz = (Class<Validator>) 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端点的输出:

    {
        "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"
    }
    

    我希望这能有所帮助