Spring: Mixing Autowired fields and constructor arguments in a base class

Issue

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 …?

Solution

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.

Answered By – Karsten Gabriel

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published