Spring Boot 3自定义校验器Native镜像运行报错求助
Spring Boot Native 自定义校验注解启动报错问题
问题背景
实现自定义校验注解,JVM模式运行正常,但生成Native可执行文件运行时抛出NoSuchMethodException,提示ClaimNumberValidator无默认构造方法。
错误堆栈
2022-11-28T17:09:40.312+01:00 ERROR 14056 --- [nio-8080-exec-1] 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 'info.novatec.validator.api.validators.ClaimNumberValidator': Failed to instantiate [info.novatec.validator.api.validators.ClaimNumberValidator]: No default constructor found] with root cause java.lang.NoSuchMethodException: info.novatec.validator.api.validators.ClaimNumberValidator.<init>() at java.base@17.0.5/java.lang.Class.getConstructor0(DynamicHub.java:3585) ~[CustomValidatorExample:na] at java.base@17.0.5/java.lang.Class.getDeclaredConstructor(DynamicHub.java:2754) ~[CustomValidatorExample:na] at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:79) ~[CustomValidatorExample:6.0.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1300) ~[CustomValidatorExample:6.0.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1198) ~[CustomValidatorExample:6.0.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[CustomValidatorExample:6.0.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[CustomValidatorExample:6.0.2] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:321) ~[CustomValidatorExample:6.0.2] at org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory.getInstance(SpringConstraintValidatorFactory.java:56) ~[na:na] at org.hibernate.validator.internal.engine.constraintvalidation.ClassBasedValidatorDescriptor.newInstance(ClassBasedValidatorDescriptor.java:84) ~[na:na] at org.hibernate.validator.internal.engine.constraintvalidation.AbstractConstraintValidatorManagerImpl.createAndInitializeValidator(AbstractConstraintValidatorManagerImpl.java:89) ~[CustomValidatorExample:8.0.0.Final] at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl.getInitializedValidator(ConstraintValidatorManagerImpl.java:117) ~[na:na] at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:136) ~[CustomValidatorExample:8.0.0.Final] at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:58) ~[na:na] at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:75) ~[CustomValidatorExample:8.0.0.Final] at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:130) ~[CustomValidatorExample:8.0.0.Final] at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:123) ~[CustomValidatorExample:8.0.0.Final] at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:555) ~[na:na] at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraints(ValidatorImpl.java:537) ~[na:na] at org.hibernate.validator.internal.engine.ValidatorImpl.validateParametersForSingleGroup(ValidatorImpl.java:991) ~[na:na] at org.hibernate.validator.internal.engine.ValidatorImpl.validateParametersForGroup(ValidatorImpl.java:932) ~[na:na] at org.hibernate.validator.internal.engine.ValidatorImpl.validateParametersInContext(ValidatorImpl.java:863) ~[na:na] at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:283) ~[na:na] at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:235) ~[na:na] at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:121) ~[na:na] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[CustomValidatorExample:6.0.2] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752) ~[na:na] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703) ~[na:na] at info.novatec.validator.api.ClaimNumberController$$SpringCGLIB$$0.isClaimNumberValid(<generated>) ~[CustomValidatorExample:na] at java.base@17.0.5/java.lang.reflect.Method.invoke(Method.java:568) ~[CustomValidatorExample:na] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[CustomValidatorExample:6.0.2] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[CustomValidatorExample:6.0.2] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[CustomValidatorExample:6.0.2] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[CustomValidatorExample:6.0.2] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[CustomValidatorExample:6.0.2] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[CustomValidatorExample:6.0.2] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080) ~[CustomValidatorExample:6.0.2] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973) ~[CustomValidatorExample:6.0.2] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003) ~[CustomValidatorExample:6.0.2] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:895) ~[CustomValidatorExample:6.0.2] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705) ~[CustomValidatorExample:6.0] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880) ~[CustomValidatorExample:6.0.2] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[CustomValidatorExample:6.0] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) ~[na:na] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[CustomValidatorExample:10.1.1] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[CustomValidatorExample:6.0.2] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CustomValidatorExample:6.0.2] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[CustomValidatorExample:6.0.2] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CustomValidatorExample:6.0.2] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[CustomValidatorExample:6.0.2] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CustomValidatorExample:6.0.2] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[na:na] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[na:na] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[CustomValidatorExample:10.1.1] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) ~[na:na] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[CustomValidatorExample:10.1.1] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[na:na] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[na:na] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) ~[na:na] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[CustomValidatorExample:10.1.1] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) ~[na:na] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739) ~[na:na] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[CustomValidatorExample:10.1.1] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[na:na] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[na:na] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[na:na] at java.base@17.0.5/java.lang.Thread.run(Thread.java:833) ~[CustomValidatorExample:na] at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:775) ~[CustomValidatorExample:na] at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:203) ~[na:na]
自定义校验器代码
import jakarta.validation.Constraint import jakarta.validation.ConstraintValidator import jakarta.validation.ConstraintValidatorContext import jakarta.validation.Payload import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER import kotlin.reflect.KClass @Target(VALUE_PARAMETER) @MustBeDocumented @Constraint(validatedBy = [ClaimNumberValidator::class]) annotation class ClaimNumber( val message: String = "ClaimNumber must 11 chars long and start with 'KR'", val groups: Array<KClass<*>> = [], val payload: Array<KClass<out Payload>> = [] ) class ClaimNumberValidator: ConstraintValidator<ClaimNumber, String> { override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean { return value!=null && value.trim().length == 11 && value.trim().startsWith("KR") } }
控制器代码
@RestController @RequestMapping("/claimNumber") @Validated class ClaimNumberController { @GetMapping("/{claimNumber}/isValid") fun isClaimNumberValid( @PathVariable @ClaimNumber claimNumber: String ): Boolean { return true } }
问题解答
1. 是否需要提交Bug报告?
需要。这个问题属于Spring Boot Native与Bean Validation集成时的反射元数据缺失问题:JVM模式下Kotlin会自动生成默认构造方法,但Native编译时GraalVM未保留该构造方法的元数据,导致Spring无法通过反射实例化校验器。这类框架层面的兼容性问题,应该向Spring Boot官方提交Bug报告,帮助完善该场景下的自动元数据生成逻辑。
2. 能否通过TypeHints解决?如何操作?
可以,通过添加类型提示让GraalVM保留校验器类的默认构造方法元数据,有两种实现方式:
方式一:使用@RegisterForReflection注解
直接在ClaimNumberValidator类上添加Spring Native的@RegisterForReflection注解,指定保留类的构造方法元数据:
import org.springframework.nativex.hint.RegisterForReflection @RegisterForReflection class ClaimNumberValidator: ConstraintValidator<ClaimNumber, String> { override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean { return value!=null && value.trim().length == 11 && value.trim().startsWith("KR") } }
方式二:创建反射配置文件
在src/main/resources/META-INF/native-image目录下创建reflect-config.json文件,添加以下配置:
[ { "name": "info.novatec.validator.api.validators.ClaimNumberValidator", "allDeclaredConstructors": true, "publicConstructors": true } ]
该配置会告知GraalVM保留指定类的所有构造方法元数据,确保Spring能通过反射正常实例化校验器。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

