I have these interfaces:
interface Query<T>
interface QueryHandler<in TQuery, TResult : Any> where TQuery : Query<TResult> {
fun execute(query: TQuery): TResult
}
and dispatcher:
abstract class QueryDispatcher {
abstract fun <TQuery, TResult : Any> dispatch(query: TQuery, queryType: KClass<TQuery>, resultType: KClass<TResult>): TResult where TQuery : Query<TResult>
inline fun <reified TQuery, reified TResult : Any> dispatch(query: TQuery) where TQuery : Query<TResult> =
dispatch(query, TQuery::class, TResult::class)
}
class InMemoryQueryDispatcher(
private val applicationContext: GenericWebApplicationContext
) : QueryDispatcher() {
override fun <TQuery : Query<TResult>, TResult : Any> dispatch(query: TQuery, queryType: KClass<TQuery>, resultType: KClass<TResult>): TResult {
val beanFactory = applicationContext.beanFactory
val resolvableType = ResolvableType.forClassWithGenerics(QueryHandler::class.java, queryType.java, resultType.java)
val beans = applicationContext.getBeanNamesForType(resolvableType)
val handler = beanFactory.getBean<QueryHandler<TQuery, TResult>>(beans.first())
...
}
}
This handler implementation with non-generic TResult
works:
class GetUserHandler() : QueryHandler<GetUser, UserDto> { ... }
but when I use another generic there:
class GetUsersHandler() : QueryHandler<GetUsers, List<UserDto>> { ... }
Spring cannot find my handler bean.
My findings:
ResolvableType.forClassWithGenerics()
produces ResolvableType
QueryHandler<GetUsers, List<?>>
, so when Spring iterates through all beans and checks the one I am looking for, ResolvableType::isAssignableFrom
matches QueryHandler
, then GetUsers
, then List
and fails on matching ?
with UserDto
- this condition returns false
My other idea was to get all nested generics inside the inlined function (so I would have [GetUser, List, UserDto]
) and pass them all to the forClassWithGenerics()
, but this fails for me because clazz
has only two "top level" types and so won't accept three.
I don't know if I am doing something wrong or if there is some problem (either bug or language limitation?), so is this somehow possible? Thank you!
Comment From: IIShabalin
Hi @leoshusar,
What you're observing must be an effect of type erasure in Java: - existed in Java 8: https://docs.oracle.com/javase/tutorial/java/generics/erasure.html - present in Java 17: https://docs.oracle.com/en/java/javase/17/docs/specs/patterns-switch-jls.html - article on Baeldung: https://www.baeldung.com/java-type-erasure
According to the docs, it's just not possible to distinguish the second type parameter of QueryHandler, because it's unbounded (defined as type Any
).
@rstoyanchev, FYI.
Comment From: snicoll
Thanks @IIShabalin.
@leoshusar type erasure is indeed the problem here.