Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlin BeanFactory.getBean extension fails with nested generic in context #31439

Closed
ianbrandt opened this issue Oct 15, 2023 · 3 comments
Closed
Assignees
Labels
theme: kotlin An issue related to Kotlin support type: enhancement A general enhancement
Milestone

Comments

@ianbrandt
Copy link
Contributor

ianbrandt commented Oct 15, 2023

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.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 15, 2023
ianbrandt added a commit to sdkotlin/sd-kotlin-spring-talks that referenced this issue Oct 15, 2023
@sdeleuze sdeleuze self-assigned this Oct 16, 2023
@sdeleuze sdeleuze added the theme: kotlin An issue related to Kotlin support label Oct 16, 2023
@ianbrandt
Copy link
Contributor Author

Added #31444 to request ParameterizedTypeReference overloads to the Java BeanFactory.getBean methods, which would presumably streamline the implementation of the Kotlin extensions.

@sdeleuze
Copy link
Contributor

sdeleuze commented Oct 17, 2023

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.

@sdeleuze sdeleuze added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Oct 18, 2023
@sdeleuze sdeleuze added this to the 6.1.0-RC2 milestone Oct 18, 2023
@ianbrandt
Copy link
Contributor Author

Thanks for the fix, @sdeleuze!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: kotlin An issue related to Kotlin support type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants