当前位置: 首页 > 知识库问答 >
问题:

使用JSR-303和spring的验证器组合实现spring bootendpoint的自定义验证逻辑

宰父涵忍
2023-03-14

我正在尝试使用JSR-303 Bean validation APIspring的Validator的组合来实现一些spring bootendpoint的自定义验证逻辑。

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

但是,如果我创建一个扩展这三个类中任何一个的验证器,并使用@initbinder注册它,它的validate(对象目标,错误错误)方法就永远不会被调用,也不会执行任何验证。如果删除@initbinder,则默认的spring验证器将执行JSR-303 Bean验证

Rest控制器:

@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(对象目标,错误错误)html" target="_blank">方法,但在此之前不执行JSR-303 Bean validation。我可以与SpringValidatorAdapter实现其JSR-303 Bean验证的方式类似地实现我的自定义JSR-303验证,但必须有一种方法来扩展它:

    @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验证文档对两者的结合并不十分清楚:

应用程序还可以为每个数据绑定器实例注册额外的spring验证器实例,如第9.8.3节“配置数据绑定器”所述。这对于插入验证逻辑而不使用注释可能很有用。

稍后将讨论如何配置多个验证器实例

还可以通过DataBinder.AddValidators和DataBinder.ReplaceValidators使用多个验证器实例配置DataBinder。当将全局配置的Bean验证与在DataBinder实例上本地配置的spring验证器组合在一起时,这很有用。看到了吗???。

我用的是spring boot 1.4.0。

共有2个答案

梁丘伟
2023-03-14

Per@m.deinum--使用addValidators()而不是setValidator()就成功了。我也同意使用JSR-303(专门用于跨字段验证的基于@AssertTrue方法的注释)可能是一个更干净的解决方案。代码示例可在https://github.com/pavelfomin/spring-boot-rest-example/tree/feature/custom-validator上获得。在本例中,中间名验证是通过自定义的spring验证器执行的,而姓氏验证是由默认的JSR303验证器处理的。

岳锦
2023-03-14

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

在我的例子中,我需要在同一个控制器中使用JSR-303和自定义验证器,通常建议使用@InitBinder,但对于我的例子来说这是不够的,因为InitBinder会在模型和验证器之间绑定(如果您使用@requestbodyinitbinder只针对每个控制器的一个模型和一个验证器)。

控制器

@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
    //...
}

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

希望这能帮上忙。

 类似资料:
  • 我正在用Spring和Thymeleaf填表: MyForm如下所示: 正如您所看到的,我做了一个自定义注释,它应该检查输入值是否可以解析为: 现在在我的Controller类中,我正在执行以下操作: 但是,当试图将放入文本字段以测试验证时,获取: 下面是我读到的示例,并希望用自定义验证器进行扩展:http://viralpatel.net/blogs/spring-mvc-hashmap-for

  • 我目前正在用Spring开发一个应用程序,使用Hibernate作为ORM。我知道,默认情况下,Hibernate在持久化或加载实体时都会使用JSR-303 Bean验证。由于此应用程序支持草稿(我希望在持久化后执行验证),因此必须将其添加到persistence.xml中: 模型实体代码: 我想知道如何传播bean验证,以便每当MyEntity实例被验证时,所有相关联的bar也被验证,而不必在验

  • 我的bean中有两个字段 当字段key=“A”,“value”应该跟在其他“key”的特定Regex后面时,它可以是任何内容。 如何根据键定义此值验证。

  • 我们有一个带有JSR注释的简单bean 我们有一个Spring控制器 我们的问题是,当调用changePassword时,验证器忽略组(PasswordChange.Class)并只验证组中不存在的firstName和lastName。 知道吗?非常感谢你抽出时间。

  • 问题内容: 我有一个具有很多用JSR-303验证注释注释的字段的bean。现在有一个新的要求,其中一个字段是强制性的,但仅在某些条件下才是强制性的。 我环顾四周,找到了我需要的验证组。 这就是我现在所拥有的: 但是,当我在单元测试中运行此验证时: 事实证明,所有非组批注的验证都将被忽略,而我仅收到1次违规。 我可以理解这种行为,但是我想知道是否有一种方法可以使该组也包括所有未注释的参数。如果没有,

  • 我使用Spring 3.2.2与Primeface 4.0和Hibernate 4.2。我在我的实体上有JSR303验证注释,并且我配置了Spring来在服务层验证它们——这很好。 但是我希望在调用服务之前启动JSF验证,但事实并非如此。我所做的所有研究都表明,我只需在类路径中添加一个验证程序,JSF2就会自动应用。 我已将建议的JAR添加到我的类路径中: 完整的依赖关系树可以在https://g