spring-attic / spring-native

Spring Native is now superseded by Spring Boot 3 official native support
https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html
Apache License 2.0
2.74k stars 355 forks source link

`@Component/@Bean` with `@ConfigurationProperties` does not work with Kotlin #1679

Closed Azbesciak closed 2 years ago

Azbesciak commented 2 years ago

Hi, I tried to find the example, but failed. I have build my app - in various place I use combination like @Component or @Bean, and @ConfigurationProperties on the class (I use kotlin. In short, it is something like this

@Component
@ConfigurationProperties(prefix = "some.name")
data class SomeNameData(
    var value: String = "",
)

or (there without defining annotations on the class)

@Configuration
class MyConfig {
    @Bean
    @ConfigurationProperties(prefix = "some.name")
    fun myData() = SomeNameData()
}

data class SomeNameData(
    var value: String = "",
)

And for normal spring app it works fine. But for spring-native I got following error

Error creating bean with name 'myData': Initialization of bean failed; nested 
exception is kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: public constructor SomeNameData(value: kotlin.String = ...) defined in com.example.demo.SomeNameData[DeserializedClassConstructorDescriptor@61ddb0ae] (member = null)
        at org.springframework.aot.beans.factory.InjectedConstructionResolver.resolve(InjectedConstructionResolver.java:88) ~[na:na]
        at org.springframework.aot.beans.factory.InjectedElementResolver.resolve(InjectedElementResolver.java:35) ~[com.example.demo.DemoApplicationKt:0.12.1]
        at org.springframework.aot.beans.factory.InjectedElementResolver.create(InjectedElementResolver.java:66) ~[com.example.demo.DemoApplicationKt:0.12.1]
        at org.springframework.aot.beans.factory.BeanDefinitionRegistrar$BeanInstanceContext.create(BeanDefinitionRegistrar.java:211) ~[na:na]
        at org.springframework.aot.ContextBootstrapInitializer.lambda$initialize$1(ContextBootstrapInitializer.java:108) ~[na:na]
        at org.springframework.aot.beans.factory.BeanDefinitionRegistrar$ThrowableFunction.apply(BeanDefinitionRegistrar.java:294) ~[com.example.demo.DemoApplicationKt:0.12.1]
        at org.springframework.aot.beans.factory.BeanDefinitionRegistrar.lambda$instanceSupplier$0(BeanDefinitionRegistrar.java:115) ~[na:na]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1249) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1191) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[com.example.demo.DemoApplicationKt:2.7.2]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[com.example.demo.DemoApplicationKt:2.7.2]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[com.example.demo.DemoApplicationKt:2.7.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[com.example.demo.DemoApplicationKt:2.7.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[com.example.demo.DemoApplicationKt:2.7.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[com.example.demo.DemoApplicationKt:2.7.2]
        at com.example.demo.DemoApplicationKt.main(DemoApplication.kt:39) ~[na:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myData': Initialization of bean failed; nested exception is kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: public constructor SomeNameData(value: kotlin.String = ...) defined in com.exam
ple.demo.SomeNameData[DeserializedClassConstructorDescriptor@61ddb0ae] (member = null)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:628) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1391) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.aot.beans.factory.InjectedConstructionResolver.lambda$resolve$0(InjectedConstructionResolver.java:83) ~[na:na]
        at org.springframework.aot.beans.factory.InjectedConstructionResolver.resolveDependency(InjectedConstructionResolver.java:97) ~[na:na]
        at org.springframework.aot.beans.factory.InjectedConstructionResolver.resolve(InjectedConstructionResolver.java:83) ~[na:na]
        ... 24 common frames omitted
Caused by: kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: public constructor SomeNameData(value: kotlin.String = ...) defined in com.example.demo.SomeNameData[DeserializedClassConstructorDescriptor@61ddb0ae] (member = null)
        at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(KFunctionImpl.kt:61) ~[na:na]
        at kotlin.reflect.jvm.ReflectJvmMapping.getJavaConstructor(ReflectJvmMapping.kt:71) ~[na:na]
        at org.springframework.beans.BeanUtils$KotlinDelegate.findPrimaryConstructor(BeanUtils.java:853) ~[na:na]
        at org.springframework.beans.BeanUtils.findPrimaryConstructor(BeanUtils.java:283) ~[na:na]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindConstructorProvider.findConstructorBindingAnnotatedConstructor(ConfigurationPropertiesBindConstructorProvider.java:57) ~[na:na]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindConstructorProvider.getBindConstructor(ConfigurationPropertiesBindConstructorProvider.java:48) ~[na:na]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBean$BindMethod.forType(ConfigurationPropertiesBean.java:317) ~[com.example.demo.DemoApplicationKt:2.7.2]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBean.<init>(ConfigurationPropertiesBean.java:78) ~[na:na]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBean.create(ConfigurationPropertiesBean.java:276) ~[na:na]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBean.get(ConfigurationPropertiesBean.java:207) ~[na:na]
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:78) ~[com.example.demo.DemoApplicationKt:2.7.2]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:440) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1796) ~[com.example.demo.DemoApplicationKt:5.3.22]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[com.example.demo.DemoApplicationKt:5.3.22]
        ... 35 common frames omitted

With @ConfigurationPropertiesScan and @ConstructorBinding as in examples - it works, but IMHO the solution with @Bean is more configurable. Am I doing something wrong? Can something be done with that?

mhalbritter commented 2 years ago

Hi! If you do a @EnableConfigurationProperties(SomeNameData) it works? It only doesn't work if you annotate it with @Component?

Azbesciak commented 2 years ago

Yes, the following app works

@SpringBootApplication
@ConfigurationPropertiesScan
class DemoApplication

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

@ConstructorBinding
@ConfigurationProperties(prefix = "some.name")
data class SomeNameData(
    var value: String = "",
)

@RequestMapping("hello")
@RestController
class HelloController(private val name: SomeNameData) {
    @GetMapping
    fun getName() = Mono.just("HELLO ${name.value}")
}

ok, the question was slightly different... need to check It does not


@SpringBootApplication
@ConfigurationPropertiesScan
class DemoApplication

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

@Configuration
@EnableConfigurationProperties
class Config

@Component
@ConfigurationProperties(prefix = "some.name")
data class SomeNameData(
    var value: String = "",
)

it also does not

@SpringBootApplication
class DemoApplication

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

@Configuration
@EnableConfigurationProperties
class Config {
    @Bean
    fun data() = SomeNameData()
}

@ConfigurationProperties(prefix = "some.name")
data class SomeNameData(
    var value: String = "",
)
mhalbritter commented 2 years ago

As there's a workaround by not annotating your configuration properties with @Component, this is unlikely to be fixed in Spring native, as this is in minimal maintenance mode. We'll look out for that usecase in the upcoming Boot 3 / Spring Framework 6 though.

Azbesciak commented 2 years ago

But the problem is that it does not work only in spring native

mhalbritter commented 2 years ago

Yes, i understand that. But the problem you described has a workaround, and we are working on the native compilation story for Boot 3 / Spring Framework 6. When Spring Boot 3 is released, you won't need spring-native anymore, it's built into Boot itself. And there we strife to support your usecase. You can read more here

Azbesciak commented 2 years ago

That is what I needed to know, thank you :)