Cascaded bean validation 2.0 not working with nested object inside Map

我只是一个虾纸丫 提交于 2020-02-24 11:14:21

问题


Although, this question has been answered I'm interested why @Validated is needed for a working cascaded validation of Map<String, @Valid Employee>.

Update 2: For some deeper understanding I've found those posts (One,Two and Three), which explains, that @Validated is neeeded to activate method level validation. With the help of this, collections can be validated, due they are no JavaBeans which are validated instead (JSR 303).


Solution: I've updated my code snippets and my repository with working code examples. All I have to do is to annotate my controller with @Validated and add some getters in Employee. MethodValidationPostProcessor is not necessary at all.

Update: I've updated my question and forked Spring Boot Rest example to add a minimal Rest API to demonstrate:

Github Repo. The example values are inside README.md!


I've got an Spring Boot 2 API to store some employees. I can pass either one Employee or either a Map<String, Employee>.

@Validated //this is the solution to activate map validation
@RestController
class EmployeeController {

  @PostMapping("/employees")
  List<Employee> newEmployee(@RequestBody @Valid Employee newEmployee) {
     ...
  }

  @PostMapping("/employees/bulk")
  List<Employee> newEmployee(@RequestBody Map<String, @Valid Employee> 
  newEmployees) {
     ...
  }
}

Employee exists of some inner static classes which also needs to be validated:

public class Employee {

    @NotBlank
    public final String name;
    @Valid
    public final EmployeeRole role;

    @JsonCreator
    public Employee(@JsonProperty("name") String name,
        @JsonProperty("role") EmployeeRole role) {

        this.name = name;
        this.role = role;
    }

    // getters

    public static class EmployeeRole {

        @NotBlank
        public String rolename;

        @Min(0)
        public int rating;

        @JsonCreator
        public EmployeeRole(@JsonProperty("rolename") String rolename,
            @JsonProperty("rating") int rating) {

            this.rolename = rolename;
            this.rating = rating;
        }

        // getters
    }
}


For now, validation for single requests are working but not for my bulk requests. As far as i know this should be possible with Bean validation 2.0.

Do you know what I've did wrong? Do i need to write a custom validator?


回答1:


To make it working you have to do following:

Add MethodValidationPostProcessor bean to configuration

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
    return new MethodValidationPostProcessor();
}

Add @Validated to your EmployeeController

@Validated
@RestController
public class EmployeeController {}'

Add @Valid to Map or to Employee

public List<Employee> newEmployee(@RequestBody @Valid Map<String, Employee> newEmployees) {}   
public List<Employee> newEmployee(@RequestBody Map<String, @Valid Employee> newEmployees) {}

That's all. This is entire EmployeeController:

@Validated
@RestController
public class EmployeeController {

    @PostMapping("/employees")
    public List<Employee> newEmployee(@RequestBody @Valid Employee newEmployee) {
        return Collections.singletonList(newEmployee);
    }

    @PostMapping("/employees/bulk")
    public List<Employee> newEmployee(@RequestBody @Valid Map<String, Employee> newEmployees) {
        return new ArrayList<>(newEmployees.values());
    }
}

And SpringBoot configuration file

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }

}

Hope it help you.




回答2:


There are two kinds of validation in spring system.

  • A: The spring boot controller methods parameter validation, only works for the http post request body data in controller with @Valid or @Validated aside
  • B: The method level validation, works for any method parameters and return values with @Validated on class and @Valid aside values to be validated

We can see that A is more narrow while B is a more common one. I'd like to answer the question on two aspects.

1 Answers are in the code

As describe in this post, the more detail part, A and B triggers method enhancement via aop by calling different method in org.hibernate.validator.internal.engine.ValidatorImpl, which leads to the difference.

  • A call validate method in ValidatorImpl via RequestResponseBodyMethodProcessor
  • B call call validateParameters method in ValidatorImpl via MethodValidationInterceptor

They are different methods with different functions, so lead to different results. You can find the answer by reading the two methods.

2 Answers are in the specification

The JSR-303 defines functions of the methods we discussed above.

validate method is explained in the validation method part, and the implementation must obey the logic defined in validation routine, in which it states that it will execute all the constraint validation for all reachable fields of the object, this is why element of List object (or other collection instance) cannot be validated via this method - the elements of the collection are not fields of the collection instance.

But validateParameters, JSR-303 actually doesn't treat it as main topic and put it in Appendix C. Proposal for method-level validation. It provides some description:

The constraints declarations evaluated are the constraints hosted on the parameters of the method or constructor. If @Valid is placed on a parameter, constraints declared on the object itself are considered.

validateReturnedValue evaluates the constraints hosted on the method itself. If @Valid is placed on the method, the constraints declared on the object itself are considered.

public @NotNull String saveItem(@Valid @NotNull Item item, @Max(23) BigDecimal price)

In the previous example,

- item is validated against @NotNull and all the constraints it hosts
- price is validated against @Max(23)
- the result of saveItem is validated against @NotNull

and exclaim that Bean Validation providers are free to implement this proposal as a specific extension. As far as I know, the Hibernate Validation project implements this method, makes constraints works on the object itself, and element of collection object.

3 Some complain

I don't know why the spring framework guys call validate in RequestResponseBodyMethodProcessor, makes lots of related questions appeare in stackoverflow. Maybe it's just because http post body data usually is a form data, and can be represented by a java bean naturally. If it's me, I'll call the validateParametes in RequestResponseBodyMethodProcessor for easy use.



来源:https://stackoverflow.com/questions/60167961/cascaded-bean-validation-2-0-not-working-with-nested-object-inside-map

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!