Forráskód Böngészése

new IOC Dependency Injection container

Exilon 5 éve
szülő
commit
8cf99ba407
1 módosított fájl, 570 hozzáadás és 0 törlés
  1. 570 0
      Quick.IOC.pas

+ 570 - 0
Quick.IOC.pas

@@ -0,0 +1,570 @@
+{ ***************************************************************************
+
+  Copyright (c) 2016-2019 Kike Pérez
+
+  Unit        : Quick.IoC
+  Description : IoC Dependency Injector
+  Author      : Kike Pérez
+  Version     : 1.0
+  Created     : 19/10/2019
+  Modified    : 12/11/2019
+
+  This file is part of QuickLib: https://github.com/exilon/QuickLib
+
+ ***************************************************************************
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ *************************************************************************** }
+
+unit Quick.IoC;
+
+{$i QuickLib.inc}
+
+interface
+
+uses
+  System.SysUtils,
+  RTTI,
+  System.TypInfo,
+  System.Generics.Collections,
+  Quick.Logger.Intf,
+  Quick.Options;
+
+type
+  TActivatorDelegate<T> = reference to function: T;
+
+  TIocRegistration = class
+  type
+    TRegisterMode = (rmTransient, rmSingleton, rmScoped);
+  private
+    fRegisterMode : TRegisterMode;
+    fIntfInfo : PTypeInfo;
+    fImplementation : TClass;
+    fActivatorDelegate : TActivatorDelegate<TValue>;
+  public
+    constructor Create;
+    property IntfInfo : PTypeInfo read fIntfInfo write fIntfInfo;
+    property &Implementation : TClass read fImplementation write fImplementation;
+    function IsSingleton : Boolean;
+    function IsTransient : Boolean;
+    function IsScoped : Boolean;
+    function AsSingleton : TIocRegistration;
+    function AsTransient : TIocRegistration;
+    function AsScoped : TIocRegistration;
+    property ActivatorDelegate : TActivatorDelegate<TValue> read fActivatorDelegate write fActivatorDelegate;
+  end;
+
+  TIocRegistrationInterface = class(TIocRegistration)
+  private
+    fInstance : IInterface;
+  public
+    property Instance : IInterface read fInstance write fInstance;
+  end;
+
+  TIocRegistrationInstance = class(TIocRegistration)
+  private
+    fInstance : TObject;
+  public
+    property Instance : TObject read fInstance write fInstance;
+  end;
+
+  TIocRegistration<T> = record
+  private
+    fRegistration : TIocRegistration;
+  public
+    constructor Create(aRegistration : TIocRegistration);
+    function AsSingleton : TIocRegistration<T>;
+    function AsTransient : TIocRegistration<T>;
+    function AsScoped : TIocRegistration<T>;
+    function DelegateTo(aDelegate : TActivatorDelegate<T>) : TIocRegistration<T>;
+  end;
+
+  IIocRegistrator = interface
+  ['{F3B79B15-2874-4B66-9B7F-06E2EBFED1AE}']
+    function GetKey(aPInfo : PTypeInfo; const aName : string = ''): string;
+    function RegisterType(aTypeInfo : PTypeInfo; aImplementation : TClass; const aName : string = '') : TIocRegistration;
+    function RegisterInstance(aTypeInfo : PTypeInfo; const aName : string = '') : TIocRegistration;
+  end;
+
+  TIocRegistrator = class(TInterfacedObject,IIocRegistrator)
+  private
+    fDependencies : TDictionary<string,TIocRegistration>;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    property Dependencies : TDictionary<string,TIocRegistration> read fDependencies write fDependencies;
+    function GetKey(aPInfo : PTypeInfo; const aName : string = ''): string;
+    function RegisterType<TInterface: IInterface; TImplementation: class>(const aName : string = '') : TIocRegistration<TImplementation>; overload;
+    function RegisterType(aTypeInfo : PTypeInfo; aImplementation : TClass; const aName : string = '') : TIocRegistration; overload;
+    function RegisterInstance(aTypeInfo : PTypeInfo; const aName : string = '') : TIocRegistration; overload;
+    function RegisterInstance<T : class>(const aName : string = '') : TIocRegistration<T>; overload;
+    function RegisterOptions<T : TOptions>(aOptions : T) : TIocRegistration<T>;
+  end;
+
+  IIocContainer = interface
+  ['{6A486E3C-C5E8-4BE5-8382-7B9BCCFC1BC3}']
+    function RegisterType(aInterface: PTypeInfo; aImplementation : TClass; const aName : string = '') : TIocRegistration;
+    function RegisterInstance(aTypeInfo : PTypeInfo; const aName : string = '') : TIocRegistration;
+    function Resolve(aServiceType: PTypeInfo; const aName : string = ''): TValue;
+    //procedure Build;
+  end;
+
+  IIocInjector = interface
+  ['{F78E6BBC-2A95-41C9-B231-D05A586B4B49}']
+  end;
+
+  TIocInjector = class(TInterfacedObject,IIocInjector)
+  end;
+
+  IIocResolver = interface
+  ['{B7C07604-B862-46B2-BF33-FF941BBE53CA}']
+    function Resolve(aServiceType: PTypeInfo; const aName : string = ''): TValue; overload;
+  end;
+
+  TIocResolver = class(TInterfacedObject,IIocResolver)
+  private
+    fRegistrator : TIocRegistrator;
+    fInjector : TIocInjector;
+    function CreateInstance(aClass : TClass) : TValue;
+  public
+    constructor Create(aRegistrator : TIocRegistrator; aInjector : TIocInjector);
+    function Resolve<T>(const aName : string = ''): T; overload;
+    function Resolve(aServiceType: PTypeInfo; const aName : string = ''): TValue; overload;
+  end;
+
+  TIocContainer = class(TInterfacedObject,IIocContainer)
+  private
+    fRegistrator : TIocRegistrator;
+    fResolver : TIocResolver;
+    fInjector : TIocInjector;
+    fLogger : ILogger;
+    function InterfaceTypeInfo(const AGUID : TGUID) : PTypeInfo;
+  class var
+    GlobalInstance: TIocContainer;
+  protected
+    class constructor Create;
+    class destructor Destroy;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    function RegisterType<TInterface: IInterface; TImplementation: class>(const aName : string = '') : TIocRegistration<TImplementation>; overload;
+    function RegisterType(aInterface: PTypeInfo; aImplementation : TClass; const aName : string = '') : TIocRegistration; overload;
+    function RegisterInstance<T : class>(const aName: string = ''): TIocRegistration<T>; overload;
+    function RegisterInstance(aTypeInfo : PTypeInfo; const aName : string = '') : TIocRegistration; overload;
+    function RegisterOptions<T : TOptions>(aOptions : TOptions) : TIocRegistration<T>;
+    function Resolve<T>(const aName : string = ''): T; overload;
+    function Resolve(aServiceType: PTypeInfo; const aName : string = ''): TValue; overload;
+    function AbstractFactory<T : class, constructor>(aClass : TClass) : T;
+  end;
+
+  EIocRegisterError = class(Exception);
+  EIocResolverError = class(Exception);
+
+  //singleton global instance
+  function GlobalContainer: TIocContainer;
+
+implementation
+
+function GlobalContainer: TIocContainer;
+begin
+  Result := TIocContainer.GlobalInstance;
+end;
+
+{ TIocRegistration }
+
+constructor TIocRegistration.Create;
+begin
+  fRegisterMode := TRegisterMode.rmTransient;
+end;
+
+function TIocRegistration.AsTransient: TIocRegistration;
+begin
+  Result := Self;
+  fRegisterMode := TRegisterMode.rmTransient;
+end;
+
+function TIocRegistration.AsSingleton : TIocRegistration;
+begin
+  Result := Self;
+  fRegisterMode := TRegisterMode.rmSingleton;
+end;
+
+function TIocRegistration.AsScoped: TIocRegistration;
+begin
+  Result := Self;
+  fRegisterMode := TRegisterMode.rmScoped;
+end;
+
+function TIocRegistration.IsTransient: Boolean;
+begin
+  Result := fRegisterMode = TRegisterMode.rmTransient;
+end;
+
+function TIocRegistration.IsSingleton: Boolean;
+begin
+  Result := fRegisterMode = TRegisterMode.rmSingleton;
+end;
+
+function TIocRegistration.IsScoped: Boolean;
+begin
+  Result := fRegisterMode = TRegisterMode.rmScoped;
+end;
+
+{ TIocContainer }
+
+class constructor TIocContainer.Create;
+begin
+  GlobalInstance := TIocContainer.Create;
+end;
+
+class destructor TIocContainer.Destroy;
+begin
+  if GlobalInstance <> nil then GlobalInstance.Free;
+  inherited;
+end;
+
+function TIocContainer.AbstractFactory<T>(aClass: TClass): T;
+begin
+  Result := fResolver.CreateInstance(aClass).AsType<T>;
+end;
+
+constructor TIocContainer.Create;
+begin
+  fLogger := nil;
+  fRegistrator := TIocRegistrator.Create;
+  fInjector := TIocInjector.Create;
+  fResolver := TIocResolver.Create(fRegistrator,fInjector);
+end;
+
+destructor TIocContainer.Destroy;
+begin
+  fInjector.Free;
+  fResolver.Free;
+  fRegistrator.Free;
+  inherited;
+end;
+
+function TIocContainer.InterfaceTypeInfo(const AGUID : TGUID) : PTypeInfo;
+var
+  ctx : TRttiContext;
+  rtype : TRttiType;
+  rtypei : TRttiInterfaceType;
+begin
+  ctx := TRttiContext.Create;
+  try
+    for rtype in ctx.GetTypes do
+      begin
+        if rtype.TypeKind = TTypeKind.tkInterface then
+        begin
+          rtypei := (rtype as TRttiInterfaceType);
+          if IsEqualGUID(rtypei.GUID,AGUID) then Exit(rtypei.Handle);
+        end;
+      end;
+  finally
+    ctx.Free;
+  end;
+  Result := nil;
+end;
+
+function TIocContainer.RegisterType<TInterface, TImplementation>(const aName: string): TIocRegistration<TImplementation>;
+begin
+  Result := fRegistrator.RegisterType<TInterface, TImplementation>(aName);
+end;
+
+function TIocContainer.RegisterType(aInterface: PTypeInfo; aImplementation: TClass; const aName: string): TIocRegistration;
+begin
+  Result := fRegistrator.RegisterType(aInterface,aImplementation,aName);
+end;
+
+function TIocContainer.RegisterInstance<T>(const aName: string): TIocRegistration<T>;
+begin
+  Result := fRegistrator.RegisterInstance<T>(aName);
+end;
+
+function TIocContainer.RegisterInstance(aTypeInfo : PTypeInfo; const aName : string = '') : TIocRegistration;
+begin
+  Result := fRegistrator.RegisterInstance(aTypeInfo,aName);
+end;
+
+function TIocContainer.RegisterOptions<T>(aOptions: TOptions): TIocRegistration<T>;
+begin
+  Result := fRegistrator.RegisterOptions<T>(aOptions).AsSingleton;
+end;
+
+function TIocContainer.Resolve(aServiceType: PTypeInfo; const aName: string): TValue;
+begin
+  Result := fResolver.Resolve(aServiceType,aName);
+end;
+
+function TIocContainer.Resolve<T>(const aName : string = ''): T;
+begin
+  Result := fResolver.Resolve<T>(aName);
+end;
+
+{ TIocRegistrator }
+
+constructor TIocRegistrator.Create;
+begin
+  fDependencies := TDictionary<string,TIocRegistration>.Create;
+end;
+
+destructor TIocRegistrator.Destroy;
+var
+  reg : TIocRegistration;
+begin
+  for reg in fDependencies.Values do
+  begin
+    if reg <> nil then
+    begin
+      //free singleton instances not interfaced
+      if (reg is TIocRegistrationInstance) and (TIocRegistrationInstance(reg).IsSingleton) then TIocRegistrationInstance(reg).Instance.Free;
+      reg.Free;
+    end;
+  end;
+  fDependencies.Free;
+  inherited;
+end;
+
+function TIocRegistrator.GetKey(aPInfo : PTypeInfo; const aName : string = ''): string;
+begin
+  {$IFDEF NEXTGEN}
+  Result := aPInfo.Name.ToString;
+  {$ELSE}
+  Result := string(aPInfo.Name);
+  {$ENDIF}
+  if not aName.IsEmpty then Result := Result + '.' + aName.ToLower;
+end;
+
+function TIocRegistrator.RegisterInstance<T>(const aName: string): TIocRegistration<T>;
+var
+  reg : TIocRegistration;
+begin
+  reg := RegisterInstance(TypeInfo(T),aName);
+  Result := TIocRegistration<T>.Create(reg);
+end;
+
+function TIocRegistrator.RegisterInstance(aTypeInfo : PTypeInfo; const aName : string = '') : TIocRegistration;
+var
+  key : string;
+begin
+  key := GetKey(aTypeInfo,aName);
+  if fDependencies.TryGetValue(key,Result) then
+  begin
+    if Result.&Implementation = aTypeInfo.TypeData.ClassType then raise EIocRegisterError.Create('Implementation is already registered!');
+  end
+  else
+  begin
+    Result := TIocRegistrationInstance.Create;
+    Result.IntfInfo := aTypeInfo;
+    Result.&Implementation := aTypeInfo.TypeData.ClassType;
+    //reg.Instance := T.Create;
+    fDependencies.Add(key,Result);
+  end;
+end;
+
+function TIocRegistrator.RegisterOptions<T>(aOptions: T): TIocRegistration<T>;
+var
+  pInfo : PTypeInfo;
+  key : string;
+  reg : TIocRegistration;
+begin
+  pInfo := TypeInfo(IOptions<T>);
+  key := GetKey(pInfo,'');
+  if fDependencies.TryGetValue(key,reg) then
+  begin
+    if reg.&Implementation = aOptions.ClassType then raise EIocRegisterError.Create('Implementation for this interface is already registered!');
+  end
+  else
+  begin
+    reg := TIocRegistrationInterface.Create;
+    reg.IntfInfo := pInfo;
+    reg.&Implementation := aOptions.ClassType;
+    TIocRegistrationInterface(reg).Instance := TOptionValue<T>.Create(aOptions);
+    fDependencies.Add(key,reg);
+  end;
+  Result := TIocRegistration<T>.Create(reg);
+end;
+
+function TIocRegistrator.RegisterType<TInterface, TImplementation>(const aName: string): TIocRegistration<TImplementation>;
+var
+  reg : TIocRegistration;
+begin
+  reg := RegisterType(TypeInfo(TInterface),TImplementation,aName);
+  Result := TIocRegistration<TImplementation>.Create(reg);
+end;
+
+function TIocRegistrator.RegisterType(aTypeInfo : PTypeInfo; aImplementation : TClass; const aName : string = '') : TIocRegistration;
+var
+  key : string;
+begin
+  key := GetKey(aTypeInfo,aName);
+  if fDependencies.TryGetValue(key,Result) then
+  begin
+    if Result.&Implementation = aImplementation then raise EIocRegisterError.Create('Implementation for this interface is already registered!');
+  end
+  else
+  begin
+    Result := TIocRegistrationInterface.Create;
+    Result.IntfInfo := aTypeInfo;
+    Result.&Implementation := aImplementation;
+    fDependencies.Add(key,Result);
+  end;
+end;
+
+{ TIocResolver }
+
+constructor TIocResolver.Create(aRegistrator : TIocRegistrator; aInjector : TIocInjector);
+begin
+  fRegistrator := aRegistrator;
+  fInjector := aInjector;
+end;
+
+function TIocResolver.CreateInstance(aClass: TClass): TValue;
+var
+  ctx : TRttiContext;
+  rtype : TRttiType;
+  rmethod : TRttiMethod;
+  rParam : TRttiParameter;
+  value : TValue;
+  values : TArray<TValue>;
+begin
+  Result := nil;
+  ctx := TRttiContext.Create;
+  try
+    rtype := ctx.GetType(aClass);
+    if rtype = nil then Exit;
+    for rmethod in TRttiInstanceType(rtype).GetMethods do
+    begin
+      if rmethod.IsConstructor then
+      begin
+        //if create don't have parameters
+        if Length(rmethod.GetParameters) = 0 then
+        begin
+          Result := rmethod.Invoke(TRttiInstanceType(rtype).MetaclassType,[]);
+          Break;
+        end
+        else
+        begin
+          for rParam in rmethod.GetParameters do
+          begin
+            value := Resolve(rParam.ParamType.Handle);
+            values := values + [value];
+          end;
+          Result := rmethod.Invoke(TRttiInstanceType(rtype).MetaclassType,values);
+          Break;
+        end;
+      end;
+    end;
+  finally
+    ctx.Free;
+  end;
+end;
+
+function TIocResolver.Resolve(aServiceType: PTypeInfo; const aName : string = ''): TValue;
+var
+  key : string;
+  reg : TIocRegistration;
+  intf : IInterface;
+begin
+  Result := nil;
+  reg := nil;
+  key := fRegistrator.GetKey(aServiceType,aName);
+  if not fRegistrator.Dependencies.TryGetValue(key,reg) then raise EIocResolverError.CreateFmt('Type "%s" not register for IOC!',[aServiceType.Name]);
+  //if is singleton return already instance if exists
+  if reg.IsSingleton then
+  begin
+    if reg is TIocRegistrationInterface then
+    begin
+      if TIocRegistrationInterface(reg).Instance <> nil then
+      begin
+        if TIocRegistrationInterface(reg).Instance.QueryInterface(GetTypeData(aServiceType).Guid,intf) <> 0 then raise EIocResolverError.CreateFmt('Implementation for "%s" not registered!',[aServiceType.Name]);
+        TValue.Make(@intf,aServiceType,Result);
+        Exit;
+      end;
+    end
+    else
+    begin
+      if TIocRegistrationInstance(reg).Instance <> nil then
+      begin
+        Result := TIocRegistrationInstance(reg).Instance;
+        Exit;
+      end;
+    end;
+  end;
+  //instance not created yet
+  if reg.&Implementation = nil then raise EIocResolverError.CreateFmt('Implemention for "%s" not defined!',[aServiceType.Name]);
+  //use activator if assigned
+  if reg is TIocRegistrationInterface then
+  begin
+    if Assigned(reg.ActivatorDelegate) then TIocRegistrationInterface(reg).Instance := reg.ActivatorDelegate().AsInterface
+      else TIocRegistrationInterface(reg).Instance := CreateInstance(reg.&Implementation).AsInterface;
+    if (TIocRegistrationInterface(reg).Instance = nil) or (TIocRegistrationInterface(reg).Instance.QueryInterface(GetTypeData(aServiceType).Guid,intf) <> 0) then raise EIocResolverError.CreateFmt('Implementation for "%s" not registered!',[aServiceType.Name]);
+    TValue.Make(@intf,aServiceType,Result);
+  end
+  else
+  begin
+    if Assigned(reg.ActivatorDelegate) then TIocRegistrationInstance(reg).Instance := reg.ActivatorDelegate().AsObject
+    else
+    begin
+      TIocRegistrationInstance(reg).Instance := CreateInstance(reg.&Implementation).AsObject;
+    end;
+    Result := TIocRegistrationInstance(reg).Instance;
+  end;
+end;
+
+function TIocResolver.Resolve<T>(const aName : string = ''): T;
+var
+  pInfo : PTypeInfo;
+begin
+  Result := Default(T);
+  pInfo := TypeInfo(T);
+
+  Result := Resolve(pInfo,aName).AsType<T>;
+end;
+
+{ TIocRegistration<T> }
+
+function TIocRegistration<T>.AsScoped: TIocRegistration<T>;
+begin
+  Result := Self;
+  fRegistration.AsScoped;
+end;
+
+function TIocRegistration<T>.AsSingleton: TIocRegistration<T>;
+begin
+  Result := Self;
+  fRegistration.AsSingleton;
+end;
+
+function TIocRegistration<T>.AsTransient: TIocRegistration<T>;
+begin
+  Result := Self;
+  fRegistration.AsTransient;
+end;
+
+constructor TIocRegistration<T>.Create(aRegistration: TIocRegistration);
+begin
+  fRegistration := aRegistration;
+end;
+
+function TIocRegistration<T>.DelegateTo(aDelegate: TActivatorDelegate<T>): TIocRegistration<T>;
+begin
+  Result := Self;
+  fRegistration.ActivatorDelegate := function: TValue
+                                     begin
+                                       Result := TValue.From<T>(aDelegate);
+                                     end;
+end;
+
+end.