In a new project generated by Spring Initializr, I created a RestController with custom ConstraintValidator and it works fine in a JVM setup:
@Validated
@RestController
@RequestMapping("/")
public class HelloController {
@GetMapping("hello")
public String hello(@RequestParam @Exists String name) {
return "Hello %s.".formatted(name);
}
}
@Component
public class ExistsValidator implements ConstraintValidator<Exists, String> {
private final DataService service;
public ExistsValidator(DataService service) {
this.service = service;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return service.exists(value);
}
}
However, for the native image it doesn't work with the error message:
ERROR 19797 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.example.nativevalidation.ExistsValidator': Failed to instantiate [com.example.nativevalidation.ExistsValidator]: No default constructor found] with root cause
java.lang.NoSuchMethodException: com.example.nativevalidation.ExistsValidator.<init>()
...
In reflect-config.json
you can see the configuration of ExistsValidator:
{
"name": "com.example.nativevalidation.ExistsValidator",
"queriedMethods": [
{
"name": "<init>",
"parameterTypes": [
"com.example.nativevalidation.DataService"
]
}
]
}
I found no issues with the jakarta.validation.constraints.*
annotations.
Is it a limitation with native-image to be able to inject a bean into a ConstraintValidator ?
Spring Boot v3.0.1
Comment From: mhalbritter
The reflect-config.json
entry only registers the constructor of the custom validator for querying, not for invocation, which is the problem here.
Comment From: sdeleuze
It looks like there are 2 issues for that use case:
- At native level: missing hint for constructor invocation as pointed out by @mhalbritter
- At AOT level: AutowireCapableBeanFactory#createBean(java.lang.Class<T>)
invocation in SpringConstraintValidatorFactory seems not able to autowire contructor arguments with AOT mode (reproducable on the JVM). I may create a dedicated issue for this unrelated problem.
Comment From: sdeleuze
The detailed reason for the AOT level issue inSpringConstraintValidatorFactory
is that ExistsValidator()
constructor invocation which does not exist is attempted and fails because:
- AutowireCapableBeanFactory#createBean(java.lang.Class<T>)
does not perform autowiring by itself unlike AutowireCapableBeanFactory#createBean(Class, int, boolean)
can do
- Without AOT mode, AutowiredAnnotationBeanPostProcessor
autowire the bean at runtime
- With AOT mode, there is no AutowiredAnnotationBeanPostProcessor
at runtime since such processing has been done AOT
- @Component
annotation on ExistsValidator
is not really used since a new prototype bean is created via @Exists
-> @Constraint(validatedBy = { ExistsValidator.class })
at least on my repro.
Comment From: sdeleuze
Draft commit I need to test and review a bit more next week before merging it : https://github.com/sdeleuze/spring-framework/commit/gh-29823.