Issue
I have a standard Spring Boot MVC app with many entities and corresponding repositories and services. There's a lot of infrastructure shared among the components so I would like to abstract that away to generic classes. The way I'm currently trying to achieve that is this (showing just the skeleton to convey the idea):
interface AbstractRepository<T> {
fun findById(entityId: Long): T
}
abstract class AbstractEntityService<T>(
private val entityRepository: AbstractRepository<T>,
) {
fun getEntity(entityId: Long): T = entityRepository.findById(entityId)
}
@Repository
interface MyRepository : AbstractRepository<MyEntity>
@Service
class MyEntityService(
myRepository: MyRepository,
/* some other dependencies */
) : AbstractEntityService<MyEntity>(myRepository) {
/* some additional methods */
}
This seems to work, i.e. I can instantiate (or autowire) MyEntityService
. Note, however, that I have to pass MyRepository
explicitly to the constructor instead of letting Spring autowire it. That isn't possible because of runtime type erasure. But so far it doesn't bother me much.
Problems arise when I want to add some logic to AbstractEntityService
which requires some other beans, i.e. something like this
@Service
abstract class AbstractEntityService<T>(
private val entityRepository: AbstractRepository<T>,
) {
@Autowired
private lateinit var otherService: OtherService
fun getEntity(entityId: Long): T
fun commonMethodUsingOtherService(): T
}
But now I have a problem because in order to autowire OtherService
I had to make my abstract service a Spring component (@Service
) which has an unwanted side effect of Spring trying to inject AbstractRepository<T>
declared in my constructor. And because of the above mentioned type erasure it finds lots of beans of AbstractRepository
type and fails.
My question: How can I convince Spring to not inject a bean into my AbstractEntityService
constructor?
Related question: Is there a technically different solution to my problem (mentioned in the first paragraph) which avoids possible framework restrictions/shortcomings? I.e. not using inheritance, structuring my code differently etc.
Solution
It turns out that this
@Service
abstract class AbstractEntityService<T>(
private val entityRepository: AbstractRepository<T>,
) {
@Autowired
private lateinit var otherService: OtherService
fun getEntity(entityId: Long): T
fun commonMethodUsingOtherService(): T
}
actually works, in spite of what OP (me) claimed in the issue. Apparently, Spring supports autowiring in abstract classes. Moreover, @Service
annotation (or generally @Component
) is understandably ignored by Spring, so it doesn't try to inject AbstractRepository<T>
into the constructor. On the other hand, Spring can inject it if declared explicitly because it supports generics as bean qualifiers:
abstract class AbstractEntityService<T> {
@Autowired
private lateinit var otherService: OtherService
@Autowired
private lateinit var entityRepository: AbstractRepository<T>
}
I really don't know what I was thinking when I posted this issue :-) Perhaps, I got confused by errors emitted by IDE which apparently is not so smart and cannot perform the generic type bean resolution, so it complains Could not autowire. No beans of 'AbstractRepository<T>' type found.
Answered By - David Kubecka
Answer Checked By - Mildred Charles (JavaFixing Admin)