Search code examples
delphidependency-injectionspring4d

Spring4D should not call inherited constructor if not all parameters can be resolved


We use the dependency injection framework from Spring4D and have an issue with the way it constructs objects if it cannot resolve all constructor parameters. In our case all parameters of the constructor of the ancestor can be resolved and it looks like that Spring4D now uses this constructor. Even if we reintroduce the constructor the descedant, it still uses the constructor of the ancestor.

However, the behavior I would expect (and prefer) is that the resolving fails if it cannot find a correct constructor for the interface you try to resolve.

Example:

TMyClass = class(TInterfacedObject, IMyClass)
  constructor Create(ARequester: IRequester);
end;

TMyDescendant = class(TMyClass, IMyDescendant)
  constructor Create(ARequester: IRequester; AnotherRequester: IOtherRequester);
end;

GlobalContainer.Resolve<IMyDescendant>

In this example it will call the constructor of TMyClass if there is no class registered that implements IOtherRequester. This means that any Guard.IsNotNull that are in the constructor of TMyDescendant doesn't work, because that constructor is never called.

Are there ways to resolve this? Of course we could override the constructor of TMyClass in TMyDescendant, but I prefer a more clean way to do this.


Solution

  • There are multiple solutions to this problem.

    1. There is a container extension for that in the unit Spring.Container.ActivatorExtension. You simple have to register it like so:

      GlobalContainer.AddExtension<TActivatorContainerExtension>;
      

      This changes the behavior of the constructor discovery for the entire container to only consider the constructors of the most derived class that has any constructors and thus avoids going back to TObject.Create if any dependencies are missing.

      Keep in mind that even though you could have written overload on your two param constructor and not having registered IOtherRequesterit would thus not go to the TMyClass constructor which it could satisfy given that you registered some IRequester. This is because RTTI does not include any information about overloads and even if it would that would mean the container then would have to implement overload resolution which can get quite complicated.

    2. Annotate your constructor with [Inject] (remember to add the unit Spring.Container.Common to the uses or it will not have any effect - depending on the compiler version you then will see the W1074 warning)

      This will force to container to only consider this ctor and ignore any other. You can in fact annotate both constructors with it - it will always find the most derived one first. If you would inherit other classes from TMyClass that don't introduce their own constructors it will then force the one from TMyClass to be used.