With 6.1.0-RC1 (and likely prior), the following test of the BeanFactory.getBean
extension fails, presumably due to erasure:
@SpringBootTest
internal class SpringGetBeanNestedGenericIT {
@Configuration
class TestConfig {
@Bean
fun listOfString(): List<String> =
listOf("Testing")
@Bean
fun listOfListOfString(): List<List<String>> =
listOf(listOf("Testing"))
}
@Test
fun `test getBean with nested generic`(
applicationContext: ApplicationContext
) {
assertThatNoException().isThrownBy {
applicationContext.getBean<List<String>>()
}
}
}
Error message showing the listOfString
and listOfListOfString
beans being seen as the same type after erasure:
java.lang.AssertionError:
Expecting code not to raise a throwable but caught
"org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'java.util.List' available: expected single matching bean but found 2: listOfString,listOfListOfString
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1310)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:484)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:332)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1192)
at org.sdkotlin.springdemo.SpringGetBeanNestedGenericIT.test_getBean_with_nested_generic$lambda$0(SpringGetBeanNestedGenericIT.kt:37)
at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:63)
at org.assertj.core.api.NotThrownAssert.isThrownBy(NotThrownAssert.java:43)
at org.sdkotlin.springdemo.SpringGetBeanNestedGenericIT.test getBean with nested generic(SpringGetBeanNestedGenericIT.kt:30)
If I remove the listOfListOfString
bean from the configuration, the test passes.
I can inject both beans just fine, i.e. the following passes:
@Test
fun `test listOfString is injected`(
@Autowired
listOfString: List<String>
) {
assertThat(listOfString).isEqualTo(LIST_OF_STRING)
}
@Test
fun `test listOfListOfString is injected`(
@Autowired
listOfListOfString: List<List<String>>
) {
assertThat(listOfListOfString).isEqualTo(LIST_OF_LIST_OF_STRING)
}
I notice the BeanFactory.getBeanProvider
extension uses the super type token approach. Perhaps this could be used as well for getBean
to address this issue:
inline fun <reified T : Any> BeanFactory.getBean(): T =
getBeanProvider<T>(ResolvableType.forType((object : ParameterizedTypeReference<T>() {}).type)).`object`
If I import that version of the extension instead, the test passes with the listOfListOfString
bean in the configuration.
Comment From: ianbrandt
Added #31444 to request ParameterizedTypeReference
overloads to the Java BeanFactory.getBean
methods, which would presumably streamline the implementation of the Kotlin extensions.
Comment From: sdeleuze
You can get the behavior you want with applicationContext.getBeanProvider<List<String>>().getObject()
or getIfAvailable()
since we are leveraging the underlying ResolvableType
based capabilities. That said, we could maybe potentially leverage that instead of getBean(T::class.java)
for the implementation of the inline fun <reified T : Any> BeanFactory.getBean(): T
Kotlin extension. I will give it more thoughts and let you know.
Comment From: ianbrandt
Thanks for the fix, @sdeleuze!