Basically, I want to make this code work:
@Component
abstract class MyBaseClass(val myArg: MyArgClass) {
@Autowired
private lateinit var myInjectedBean: MyInjectedBean
fun useBothArgAndBean()
}
class MyConcreteClass(myArg: MyArgClass) : MyBaseClass(myArg)
val myConcreteClass = MyConcreteClass(obtainMyArgClass())
myConcreteClass.useBothArgAndBean()
So I have a base class with one argument in the constructor and another argument injected from the Spring context. Currently, such a setup is not possible because Spring tries to inject also MyArgClass
from context and because there's no such bean (it's constructed manually) it fails on "no matching bean".
The question is how to make this scenario work. Note that I cannot use the factory-method solution mentioned here https://stackoverflow.com/a/58790893/13015027 because I need to directly call MyBaseClass
constructor from MyConcreteClass
constructor. Perhaps there's a trick on how to avoid that or how to force Spring not to try to inject into the base constructor or ...?
CodePudding user response:
I think you still do need some sort of factory. It would pass both the bean and the additional arguments to the MyConcreteClass
constructor, and would look like this:
@Component
class MyFactory(val myInjectedBean: MyInjectedBean) {
fun getMyConcreteClass(myArg: MyArgClass) =
MyConcreteClass(myArg, myInjectedBean)
}
If using that approach, none of the other classes except MyInjectedBean
would need to be registered with Spring.
In fact, it's a little surprising to me that you currently have MyBaseClass
annotated with @Component
. What do you expect that to do, and does it work?
CodePudding user response:
You have a quite confusing setup there, and I am not sure that you are fully aware how injection by Spring works. You can
- either create a class on your own, using its constructor, or
- you can let Spring create the class and inject everything, and you don't call the constructor.
When you call the constructor, Spring will not magically inject some parts of your class, just because it has seemingly the right annotations. The variable myInjectedBean
will just be null.
If you want to have the ability to create the class on your own using the constructor, you should not use field injection, because you would obviously not have any possibility to initialize the field.
Then your classes MyBaseClass
and MyConcreteClass
would look like this:
abstract class MyBaseClass(
val myArg: MyArgClass,
private val myInjectedBean: MyInjectedBean
) {
fun useBothArgAndBean()
}
class MyConcreteClass(myArg: MyArgClass, myInjectedBean: MyInjectedBean) : MyBaseClass(myArg, myInjectedBean)
Now, as already suggested by @Sam, you can have myInjectedBean
be injected while providing myArg
manually by writing another component that can completely be created by Spring, because it will only autowire myInjectedBean
while myArg
is provided as argument for a factory method:
@Component
class MyFactory(val myInjectedBean: MyInjectedBean) {
fun createMyConcreteClass(myArg: MyArgClass) =
MyConcreteClass(myArg, myInjectedBean)
}
Then in a class, where you have an injected myFactory: MyFactory
you can just call myFactory.createMyConcreteClass(myArg)
and you obtain a new MyConcreteClass
that has an injected myInjectedBean
.