[spring-projects/spring-boot]对具有意外根本原因(例如 ArrayIndexOutOfBoundsException)的 BindException 进行故障分析是没有帮助的

2024-04-17 66 views
1

你好,

我正在使用 Spring Boot 2.0.2 和 Kotlin 1.2.41。我正在尝试使用枚举作为 ConfigurationProperty。我编写了一个最小的示例应用程序来重现该问题: https://github.com/sbueringer/springboot-kotlin-enum/

我有以下配置属性:

@Configuration
@ConfigurationProperties(prefix = "custom")
@Validated
class Properties {
    var test = TestEnum.VALUE1
}
enum class TestEnum {VALUE1, VALUE2}

与以下 application.yaml 结合使用:

custom:
  test: VALUE2

我在启动时遇到以下异常:

2018-05-25 06:45:21.848  WARN 22061 --- [  restartedMain] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'application': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name 'properties': Could not bind properties to 'Properties' : prefix=custom, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'custom.test' to com.example.TestEnum

要重现,只需克隆存储库并启动 SpringBootApplication。

编辑:如果我删除注释,它似乎可以工作@Validated。这是故意的吗?

回答

8

这关闭得太快了。更改配置属性后,lateinit var错误有所不同。我刚刚在将应用程序迁移到 Spring Boot 2 时遇到了这个问题。

@Configuration
@ConfigurationProperties(prefix = "custom")
@Validated
class Properties {
    lateinit var test: TestEnum
}

错误:

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to bind properties under 'custom.test' to com.example.TestEnum:

    Property: custom.test
    Value: VALUE2
    Origin: class path resource [application.yaml]:3:9
    Reason: 0

这是我对同一存储库进行此更改的版本:https://github.com/crypticmind/springboot-kotlin-enum

7

@snicoll 请看一下并告诉我是否应该打开一个新问题。

8

运行调试给出:

org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'custom.test' to com.example.TestEnum
        at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:250) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:226) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$4(Binder.java:334) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:73) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:62) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.lambda$null$5(Binder.java:342) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_144]
        at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1351) ~[na:1.8.0_144]
        at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_144]
        at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498) ~[na:1.8.0_144]
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485) ~[na:1.8.0_144]
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_144]
        at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_144]
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_144]
        at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464) ~[na:1.8.0_144]
        at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$6(Binder.java:343) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:442) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:428) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder$Context.access$400(Binder.java:382) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:340) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:279) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:221) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:210) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:192) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:82) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:107) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:93) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:422) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1698) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:579) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:815) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:721) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:192) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1274) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1131) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:541) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:760) ~[spring-beans-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869) ~[spring-context-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:61) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:395) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:327) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at com.example.ApplicationKt.main(Application.kt:13) [main/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_144]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_144]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_144]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_144]
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.0.2.RELEASE.jar:2.0.2.RELEASE]
Caused by: java.lang.ArrayIndexOutOfBoundsException: 0
        at java.util.Arrays$ArrayList.get(Arrays.java:3841) ~[na:1.8.0_144]
        at org.hibernate.validator.internal.metadata.aggregated.ParameterMetaData$Builder.build(ParameterMetaData.java:169) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.findParameterMetaData(ExecutableMetaData.java:435) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.build(ExecutableMetaData.java:388) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BuilderDelegate.build(BeanMetaDataImpl.java:788) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BeanMetaDataBuilder.build(BeanMetaDataImpl.java:648) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at org.hibernate.validator.internal.metadata.BeanMetaDataManager.createBeanMetaData(BeanMetaDataManager.java:192) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at org.hibernate.validator.internal.metadata.BeanMetaDataManager.lambda$getBeanMetaData$0(BeanMetaDataManager.java:160) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:324) ~[na:1.8.0_144]
        at org.hibernate.validator.internal.metadata.BeanMetaDataManager.getBeanMetaData(BeanMetaDataManager.java:159) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at org.hibernate.validator.internal.engine.ValidationContext$ValidationContextBuilder.forValidate(ValidationContext.java:566) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:155) ~[hibernate-validator-6.0.9.Final.jar:6.0.9.Final]
        at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:104) ~[spring-context-5.0.6.RELEASE.jar:5.0.6.RELEASE]
        at org.springframework.boot.context.properties.ConfigurationPropertiesJsr303Validator.validate(ConfigurationPropertiesJsr303Validator.java:52) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.validation.ValidationBindHandler.lambda$validate$1(ValidationBindHandler.java:101) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:1.8.0_144]
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) ~[na:1.8.0_144]
        at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) ~[na:1.8.0_144]
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_144]
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_144]
        at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:1.8.0_144]
        at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:1.8.0_144]
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_144]
        at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) ~[na:1.8.0_144]
        at org.springframework.boot.context.properties.bind.validation.ValidationBindHandler.validate(ValidationBindHandler.java:101) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.validation.ValidationBindHandler.validate(ValidationBindHandler.java:83) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.validation.ValidationBindHandler.onFinish(ValidationBindHandler.java:72) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.handleBindResult(Binder.java:236) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:223) ~[spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
        ... 61 common frames omitted
4

故障分析肯定会更好。

当 Hibernate Validator 尝试获取构造函数的第一个(索引 0)参数的名称时,会发生失败protected com.example.TestEnum(java.lang.String,int)。这会导致getParameterNames(Constructor<?> constructor)在 Spring 框架中调用此代码LocalValidatorFactoryBean

configuration.parameterNameProvider(new ParameterNameProvider() {
    @Override
    public List<String> getParameterNames(Constructor<?> constructor) {
        String[] paramNames = discoverer.getParameterNames(constructor);
        return (paramNames != null ? Arrays.asList(paramNames) :
                defaultProvider.getParameterNames(constructor));
    }
    @Override
    public List<String> getParameterNames(Method method) {
        String[] paramNames = discoverer.getParameterNames(method);
        return (paramNames != null ? Arrays.asList(paramNames) :
                defaultProvider.getParameterNames(method));
    }
});

discoverer、 a PrioritizedParameterNameDiscoverer、 返回零长度字符串数组。当 Hibernate Validator 尝试检索数组索引 0 处的条目时,它会失败并出现上述异常。空数组来自委托,KotlinReflectionParameterNameDiscoverer因为它的getParameterNames(List<KParameter>)方法是用空列表调用的。

我不确定这是 Kotlin 错误还是 Spring 框架错误。希望@sdeleuze 能够指出正确的方向。

0

我已经打开了HV-1638,以防 Hibernate Validator 团队有兴趣使其对返回过少名称的名称提供程序更具弹性。

9

它可以链接到SPR-16931以及我提出的相关KT-25165 。我添加了一条评论,提到了这对我们可能产生的新影响。

5

谢谢你,@sdeleuze。 SPR-16931 和 KT-25165 看起来正是我们所看到的问题。

鉴于这些问题已经涵盖了根本问题,让我们利用这个问题来改进故障分析。 “原因:0”根本没有帮助。在这种情况下,我们也许应该选择完全退出故障分析(如果可以的话)。

1

我不确定 1.5 是否也会发生这种情况。至少现在分配给 1.5.x。

9

@wilkinsona 关于 1.5 中没有发生的情况,您指的是根本问题,还是晦涩的错误消息?我在 1.5 中的 Kotlin 枚举中没有遇到过这个问题,至于错误消息,我知道这是由新绑定 API 中的错误报告生成的。显然,在 1.5 中检查这一点将是令人满意的彻底,但我敢打赌这种情况只会发生在 2.0 版本中。

2

我指的是晦涩的错误消息。 Kotlin 支持是 Framework 5 和 Boot 2.0 中的新增功能,因此它不会成为导致模糊错误消息的可能原因。虽然异常来自活页夹,但错误消息来自BindFailureAnalyzer.它存在于 1.5 和 2.0 中,尽管两者并不相同,并且输入也会不同,因此需要检查 1.5 中发生的情况。

7

不幸的是,我认为我们对此无能为力。我找不到一个好的启发式方法来检测异常消息是否有用。如果没有其中之一,我们面临的两个选择就是让事情保持原样,或者总是假设它没有用。如果我们选择后者,分析仪的好处将在很大程度上被抵消。我想我们只能忍受这个。我们可以重新考虑问题是否再次出现,其原因不是由于另一个库中的错误所致。

9

@wilkinsona @sbueringer @philwebb

很抱歉打扰您,但我有一个问题。

我们可以调整KotlinReflectionParameterNameDiscoverer来解决这个问题吗?

在我看来,一切都在我们手中,我们不需要等待 Kotlin 的任何东西。

看起来,如果我们添加枚举检查,就可以解决问题。我在方法的开头添加了以下几行,它对我有用。KotlinReflectionParameterNameDiscoverer.getParameterNames(Constructor<?> ctor):

if (ctor.getDeclaringClass().isEnum()) {
    return null;
}

根据PrioritizedParameterNameDiscoverer.getParameterNames(Constructor<?> ctor)ParameterNameDiscoverers 将被应用,直到其中之一不会返回 null。

因此,当返回 null 时,将应用KotlinReflectionParameterNameDiscoverer以下内容并返回默认枚举的参数,这不会导致错误。StandardReflectionParameterNameDiscoverer{ "$enum$name", "$enum$oridnal"}

看起来 JetBrains 的 Alexander Udalov已经明确澄清:对于 Kotlin 语言,Enum 没有未声明的参数。

因此,您是否认为对于 Kotlin Enums,我们应该将参数发现委托给 Java?上面的代码有帮助吗?

4

@ruslanys 感谢您的浏览。KotlinReflectionParameterNameDiscoverer是 Spring 框架的一部分,因此这里不适合讨论它。我认为SPR-16931是一个更好的讨论场所。

6

@wilkinsona 谢谢您的回复。 ?