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

Time:08-31

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.

  • Related