You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring4D中实现作用域上下文及作用域单例?

实现Delphi依赖注入中的作用域单例

核心需求

需要实现一种作用域内单例的生命周期管理:

  • 行为类似TSingletonPerThreadLifetimeManager,但实例会在作用域结束时自动释放
  • 作用域内的多个依赖对象(如示例中的TTest和TSub)共享同一个实例,彼此无需感知对方的存在
  • 避免在作用域代码中直接引用容器,通过工厂函数封装作用域逻辑

问题分析

  1. 直接使用AsScoped可能不符合预期(取决于具体DI容器实现)
  2. 尝试的委托注册会触发递归:调用Container.Resolve<IContext>会再次进入委托逻辑
  3. TSingletonPerThreadLifetimeManager无法在作用域结束时主动释放实例

解决方案一:修复委托注册的递归问题

修改委托逻辑,直接创建实例而非调用容器Resolve,避免递归:

Container.RegisterType<IContext, TContext>.DelegateTo(
  function: IContext
  var
    ContextInst: IContext;
  begin
    if TScope.InScope then
    begin
      if not TScope.Has<IContext> then
      begin
        // 直接创建实例,跳过容器Resolve环节避免递归
        ContextInst := TContext.Create;
        TScope.Add<IContext>(ContextInst);
      end;
      Result := TScope.Get<IContext>;
    end
    else
    begin
      // 不在作用域时返回新实例(可按需调整逻辑)
      Result := TContext.Create;
    end;
  end
);

解决方案二:自定义作用域生命周期管理器(更优雅)

如果使用Spring4D等DI框架,可自定义继承自TLifetimeManager的管理器,与TScope绑定实现作用域内单例:

1. 自定义生命周期管理器

type
  TScopedSingletonLifetimeManager = class(TLifetimeManager)
  protected
    function GetValue: TValue; override;
    procedure SetValue(const Value: TValue); override;
    procedure RemoveValue; override;
  public
    destructor Destroy; override;
  end;

destructor TScopedSingletonLifetimeManager.Destroy;
begin
  RemoveValue;
  inherited;
end;

function TScopedSingletonLifetimeManager.GetValue: TValue;
begin
  if TScope.InScope then
  begin
    if not TScope.Has<TValue>(Self) then
      Result := inherited GetValue
    else
      Result := TScope.Get<TValue>(Self);
  end
  else
    Result := inherited GetValue;
end;

procedure TScopedSingletonLifetimeManager.SetValue(const Value: TValue);
begin
  if TScope.InScope then
    TScope.Add<TValue>(Self, Value)
  else
    inherited SetValue(Value);
end;

procedure TScopedSingletonLifetimeManager.RemoveValue;
begin
  if TScope.InScope then
    TScope.Remove<TValue>(Self)
  else
    inherited RemoveValue;
end;

2. 注册类型时使用自定义管理器

Container.RegisterType<IContext, TContext>.LifetimeManager(TScopedSingletonLifetimeManager.Create);
Container.RegisterType<ITest, TTest>.AsTransient;
Container.RegisterType<ISub, TSub>.AsTransient;

3. 完善TScope的销毁清理逻辑

在TScope销毁时遍历释放所有存储的实例:

type
  TScope = class
  private
    FInstances: TDictionary<TObject, TValue>;
    class var FCurrentScope: TScope;
    // 其他成员省略
  public
    class function New: TScope; static;
    class function InScope: Boolean; static;
    class function Has<T>(Key: TObject): Boolean; static;
    class procedure Add<T>(Key: TObject; const Value: T); static;
    class function Get<T>(Key: TObject): T; static;
    class procedure Remove<T>(Key: TObject); static;
    destructor Destroy; override;
  end;

destructor TScope.Destroy;
var
  Pair: TPair<TObject, TValue>;
begin
  for Pair in FInstances do
  begin
    if Pair.Value.IsObject then
      Pair.Value.AsObject.Free;
    // 显式清空值,加速接口引用计数释放
    Pair.Value := nil;
  end;
  FInstances.Free;
  FCurrentScope := nil;
  inherited;
end;

作用域工厂的正确使用

保持你期望的工厂封装方式,确保在作用域内调用工厂函数:

procedure DoInScope(TestFactory: Func<ITest>);
begin
  var Scope := TScope.New;
  try
    TestFactory().Execute;
  finally
    Scope := nil;
  end;
end;

// 调用示例
DoInScope(function: ITest
  begin
    Result := Container.Resolve<ITest>;
  end);

关键注意事项

  • 多线程场景下,需为TScope的FCurrentScope和FInstances添加线程锁保证安全
  • 接口类型实例虽由引用计数自动管理,但作用域结束时显式清理可避免延迟释放
  • 自定义生命周期管理器需适配你使用的DI框架的接口规范

内容的提问来源于stack exchange,提问作者Richard Shuck

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 13:17:29