问题
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 yourEmployeeController
@Validated
@RestController
public class EmployeeController {}'
Add
@Valid
toMap
or toEmployee
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 inValidatorImpl
viaRequestResponseBodyMethodProcessor
- B call call
validateParameters
method inValidatorImpl
viaMethodValidationInterceptor
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