AOP和DI:如何创建一个方面感知的接口依赖注入对象



我想通过Spring/4D框架创建一个方面感知的接口依赖注入对象。我的问题是,我不知道如何将这两部分结合起来。总体思路如下:

  1. 创建方面层对象并为其保留两个接口:一个作为对对象的依赖项传递(IAspect),另一个作为方面编织到对象中(IInterceptor):

    Temp := TAspect.Create;
    Aspect := Temp as IAspect;
    Interceptor := Temp as IInterceptor;
    
  2. 创建接口依赖项注入对象:

    Instance := TInstance.Create(Aspect) {as IInstance};
    
  3. 编织方面:

    Result := TProxyGenerator.CreateInterfaceProxyWithTarget(Instance, [Interceptor]);
    

为了解决这个问题,我想用一个自定义构造函数注册一个工厂,如下所示:

Aspect := Resolve<IAspect>;
Interceptor := Aspect as IInterceptor;
Instance := InstanceFactory(Aspect); // InstanceFactory := Resolve<IInstanceFactory>;
Result := TProxyGenerator.CreateInterfaceProxyWithTarget(Instance, [Interceptor]);

我的问题是,如何在Spring的Container: TContainer中注册这个?


示例:下面的程序的行为与我想要的一样,并演示了GetValue调用通过哪个方面层运行。自定义对象的创建发生在主例程的$Region中。我需要如何重构这个程序以使用Spring/4D框架中的DI容器,同时保留方面感知对象的自定义构造?

program Project1;
{$AppType Console}
{$R *.res}
uses
System.SysUtils,
Spring,
Spring.Interception;
type
IAspect = interface
['{AF8E19F6-176D-490E-A475-4682336CAB89}']
function GetSetting: String;
procedure SetSetting(const Value: String);
property Setting: String read GetSetting write SetSetting;
end;
TAspect = class (TInterfacedObject, IInterceptor, IAspect)
strict private
FSetting: String;
function GetSetting: String;
procedure SetSetting(const Value: String);
procedure Intercept(const Invocation: IInvocation);
public
constructor Create;
end;
IThingy = interface (IInvokable)
function GetAspect: IAspect;
function GetValue: String;
procedure SetValue(const Value: String);
property InstanceAspect: IAspect read GetAspect;
property Value: String read GetValue write SetValue;
end;
TThingy = class (TInterfacedObject, IThingy)
strict private
FInstanceAspect: IAspect;
FClassAspect: IAspect;
FValue: String;
function GetAspect: IAspect;
function GetValue: String;
procedure SetValue(const Value: String);
public
constructor Create(const InstanceAspect, ClassAspect: IAspect);
end;
{ TAspect }
constructor TAspect.Create;
begin
inherited;
FSetting := ' intercepted by class aspect';
end;
function TAspect.GetSetting: String;
begin
Result := FSetting;
end;
procedure TAspect.Intercept(
const Invocation: IInvocation);
begin
Invocation.Proceed;
if Invocation.Method.Name = 'GetValue' then
Invocation.Result := TValue.From<String>(Invocation.Result.AsString + FSetting);
end;
procedure TAspect.SetSetting(
const Value: String);
begin
FSetting := Value;
end;
{ TThingy }
constructor TThingy.Create(const InstanceAspect, ClassAspect: IAspect);
begin
inherited Create;
FInstanceAspect := InstanceAspect;
FClassAspect := ClassAspect;
FValue := 'Value';
end;
function TThingy.GetAspect: IAspect;
begin
Result := FInstanceAspect;
end;
function TThingy.GetValue: String;
begin
Result := FValue;
end;
procedure TThingy.SetValue(const Value: String);
begin
FValue := Value;
end;
{ Main }
procedure Main;
var
Temp: TInterfacedObject;
ClassAspect: IAspect;
ClassInterceptor: IInterceptor;
InstanceAspect: IAspect;
InstanceInterceptor: IInterceptor;
Thingy1: IThingy;
Thingy2: IThingy;
begin
{$Region 'How to do this with the Spring DI container?'}
Temp := TAspect.Create;
ClassAspect := Temp as IAspect;
ClassInterceptor := Temp as IInterceptor;
Temp := TAspect.Create;
InstanceAspect := Temp as IAspect;
InstanceInterceptor := Temp as IInterceptor;
Thingy1 := TThingy.Create(InstanceAspect, ClassAspect);
Thingy1 := TProxyGenerator.CreateInterfaceProxyWithTarget(Thingy1, [ClassInterceptor, InstanceInterceptor]);
Temp := TAspect.Create;
InstanceAspect := Temp as IAspect;
InstanceInterceptor := Temp as IInterceptor;
Thingy2 := TThingy.Create(InstanceAspect, ClassAspect);
Thingy2 := TProxyGenerator.CreateInterfaceProxyWithTarget(Thingy2, [ClassInterceptor, InstanceInterceptor]);
{$EndRegion}
Thingy1.InstanceAspect.Setting := ' intercepted by instance aspect 1';
Thingy2.InstanceAspect.Setting := ' intercepted by instance aspect 2';
Thingy1.Value := 'Value 1';
Thingy2.Value := 'Value 2';
WriteLn(Format('Thingy1.Value: %s', [Thingy1.Value]));
WriteLn(Format('Thingy2.Value: %s', [Thingy2.Value]));
end;
begin
try
Main;
except
on E: Exception do
WriteLn(E.ClassName, ': ', E.Message);
end;
if DebugHook <> 0 then
begin
WriteLn('Press enter...');
ReadLn;
end;
end.

输出:

Thingy1.Value: Value 1 intercepted by instance aspect 1 intercepted by class aspect
Thingy2.Value: Value 2 intercepted by instance aspect 2 intercepted by class aspect
Press enter...

我不完全确定你到底想实现什么,但这里是如何设置容器以获得你想要的结果。目前还不起作用的是上下文注入(在解析过程中根据当前构建的对象图做出决策)——这是我们计划在未来实现的。

program Project1;
{$AppType Console}
{$R *.res}
uses
System.SysUtils,
Spring,
Spring.Container,
Spring.Interception;
type
IThingy = interface (IInvokable)
['{FD337CC6-03EB-4384-A027-E993AB687BF0}']
function GetValue: String;
procedure SetValue(const Value: String);
property Value: String read GetValue write SetValue;
end;
TThingy = class (TInterfacedObject, IThingy)
strict private
FValue: String;
function GetValue: String;
procedure SetValue(const Value: String);
end;
{ TThingy }
function TThingy.GetValue: String;
begin
Result := FValue;
end;
procedure TThingy.SetValue(const Value: String);
begin
FValue := Value;
end;
type
TClassInterceptor = class(TInterfacedObject, IInterceptor)
procedure Intercept(const Invocation: IInvocation);
end;
TInstanceInterceptor = class(TInterfacedObject, IInterceptor)
private
class var InstanceCount: Integer;
var FNo: Integer;
procedure Intercept(const Invocation: IInvocation);
public
constructor Create;
end;
{ Main }
procedure Main;
var
Thingy1: IThingy;
Thingy2: IThingy;
begin
GlobalContainer.RegisterType<TClassInterceptor,TClassInterceptor>.AsSingleton;
GlobalContainer.RegisterType<TInstanceInterceptor>('instance');
GlobalContainer.RegisterType<IThingy, TThingy>.InterceptedBy<TClassInterceptor>.InterceptedBy('instance');
GlobalContainer.Build;
Thingy1 := GlobalContainer.Resolve<IThingy>;
Thingy2 := GlobalContainer.Resolve<IThingy>;
Thingy1.Value := 'Value 1';
Thingy2.Value := 'Value 2';
WriteLn(Format('Thingy1.Value: %s', [Thingy1.Value]));
WriteLn(Format('Thingy2.Value: %s', [Thingy2.Value]));
end;
procedure TClassInterceptor.Intercept(const Invocation: IInvocation);
begin
Invocation.Proceed;
if Invocation.Method.Name = 'GetValue' then
Invocation.Result := TValue.From<String>(Invocation.Result.AsString + ' intercepted by class aspect');
end;
constructor TInstanceInterceptor.Create;
begin
Inc(InstanceCount);
FNo := InstanceCount;
end;
procedure TInstanceInterceptor.Intercept(const Invocation: IInvocation);
begin
Invocation.Proceed;
if Invocation.Method.Name = 'GetValue' then
Invocation.Result := TValue.From<String>(Invocation.Result.AsString + ' intercepted by instance aspect ' + IntToStr(FNo));
end;
begin
try
Main;
except
on E: Exception do
WriteLn(E.ClassName, ': ', E.Message);
end;
if DebugHook <> 0 then
begin
WriteLn('Press enter...');
ReadLn;
end;
end.

最新更新