3

I'm struggling with the constructor injection of Spring4D. In a certain class i want to inject a specific implementation (by name) of an interface into the constructor.

Look at this:

IListFactory = interface
    ['{40...29}']
    function GetList : IListOfSomething;
end;

ICiderPress = interface
    ['{50...10}']
    procedure Press;
end;

TAppleListFactory = class(TInterfacedObject, IListFactory)
    function GetList : IListOfSomething;
end;

TCiderPress = class(TInterfacedObject, ICiderPress)
private
    FListFactory : IListFactory;
public
    constructor Create(const ListFactory : IListFactory);

    procedure Press;
end;

implementation

function TCiderPress.Create(const ListFactory : IListFactory);
begin
    FListFactory := ListFactory;
end;

procedure TCiderPress.Press;
begin
    // Do somtihing with FListFactory
end;

initialization
    GlobalContainer.RegisterType<TAppleListFactory>.Implements<IListFactory>('apple');

    GlobalContainer.RegisterType<TCiderPress>.Implements<ICiderPress>;
end.

Now I get an instance of my press with the ServiceLocator:

CiderPress := ServiceLocator.GetService<ICiderPress>;
CiderPress.Press;

and it works fine.

Now I add a second ListFactory:

TOrangeListFactory = class(TInterfacedObject, IListFactory)
    function GetList : IListOfSomething;
end;

and add the registration

GlobalContainer.RegisterType<TOrangeListFactory>.Implements<IListFactory>('orange');

and change my cidre press class to

TCiderPress = class(TInterfacedObject, ICiderPress)
private
    FListFactory : IListFactory;
public
    [Inject]
    constructor Create([Inject('apple')]const ListFactory : IListFactory);

    procedure Press;
end;

The Problem is, that the ctor of TCiderPress is not called.

If I add

GlobalContainer.AddExtension<TActivatorContainerExtension>;

I get an EActivatorException: Unsatisfied contructor on type: TCiderPress

What's going wrong?

EDIT:

It works, if i delegate the construction like this:

GlobalContainer.RegisterType<TCiderPress>.Implements<ICiderPress>
    .DelegateTo(function : TCiderPress
        begin
            Result := TCiderPress.Create(ServiceLocator.GetService<IListFactory>('apple');
        end
    );

EDIT2:

I found my error! I had to include Spring.Container.Common in the interface uses clause.

I'm using Delphi XE3 and Spring4D 1.1.3.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Mik
  • 35
  • 5
  • Looks like we need a [mcve] – David Heffernan Mar 11 '16 at 07:36
  • 1
    The example from @mezen works for me. My real app ist quite complex. I try to reproduce the error with a minimal example. Until then, is there a way to debug the constructor injection? – Mik Mar 11 '16 at 08:32
  • Anything can be debugged. Making a repro is how I would debug it. – David Heffernan Mar 11 '16 at 08:40
  • This would be a better question with a [mcve], then it could serve as help for future visitors – David Heffernan Mar 11 '16 at 09:40
  • @Mik without Spring.Container.Common in uses, the compiler complains: [dcc32 Warning] Unit1.pas(20): W1025 Unsupported language feature: 'custom attribute', That was enough to me to see what was missing, Is the warnings off in your project? – Cesar Romero Mar 11 '16 at 11:59
  • @Cesar The warning was shown, but hidden under a big pile of other warnings. It's a problem in this certain project. I've got the hint to let the compiler treat this warning as an error. This is the first action for me until all compiler warnings are cleaned up in the future. – Mik Mar 11 '16 at 13:48

1 Answers1

5

This works for me:

unit Unit2;

interface

uses
  Spring.Container,
  Spring.Container.Common;

type
  IListOfSomething = interface
  ['{ACCEF350-5FDE-4D60-BAE0-17F029A669ED}']
  end;


  IListFactory = interface
  ['{039DE93A-1235-4D75-A8E2-7265765F6E90}']
    function GetList : IListOfSomething;
  end;

  ICiderPress = interface
  ['{64C4F565-BB8C-42C0-9584-4F4A21779F52}']
    procedure Press;
  end;

  TAppleListFactory = class(TInterfacedObject, IListFactory)
  public
    function GetList: IListOfSomething;
  end;

  TOrangeListFactory = class(TInterfacedObject, IListFactory)
  public
    function GetList: IListOfSomething;
  end;

  TCiderPress = class(TInterfacedObject, ICiderPress)
  private
    FListFactory: IListFactory;
  public
    [Inject]
    constructor Create([Inject('apple')] const ListFactory: IListFactory);
    procedure Press;
  end;

implementation

constructor TCiderPress.Create(const ListFactory: IListFactory);
begin
  FListFactory := ListFactory;
end;

procedure TCiderPress.Press;
begin
  WriteLn(TObject(FListFactory).ClassName);
end;

{ TAppleListFactory }

function TAppleListFactory.GetList: IListOfSomething;
begin
  Result := nil;
end;

{ TOrangeListFactory }

function TOrangeListFactory.GetList: IListOfSomething;
begin
  Result := nil;
end;

initialization
  GlobalContainer.RegisterType<TAppleListFactory>.Implements<IListFactory>('apple');
  GlobalContainer.RegisterType<TOrangeListFactory>.Implements<IListFactory>('orange');
  GlobalContainer.RegisterType<TCiderPress>.Implements<ICiderPress>;
  GlobalContainer.Build();
end.

and consuming like

o := GlobalContainer.Resolve<ICiderPress>;
o.Press();
mezen
  • 198
  • 4
  • Hi, thank you for your test. I think, I have described your code and thought, I did exaclty the same in my app. I tested your code in my app and it works. Thus there seems to be no problems with the Delphi or Spring4D version. I need to figure out, where the differences are between this example code and my (more complex) app code... – Mik Mar 11 '16 at 07:28
  • 1
    Your example point me to the solution. I needed to include the _Spring.Container.Common_ into the uses clause. – Mik Mar 11 '16 at 09:27