如何在Spring4D中实现作用域上下文及作用域单例?
实现Delphi依赖注入中的作用域单例
核心需求
需要实现一种作用域内单例的生命周期管理:
- 行为类似
TSingletonPerThreadLifetimeManager,但实例会在作用域结束时自动释放 - 作用域内的多个依赖对象(如示例中的
TTest和TSub)共享同一个实例,彼此无需感知对方的存在 - 避免在作用域代码中直接引用容器,通过工厂函数封装作用域逻辑
问题分析
- 直接使用
AsScoped可能不符合预期(取决于具体DI容器实现) - 尝试的委托注册会触发递归:调用
Container.Resolve<IContext>会再次进入委托逻辑 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
相关产品推荐
相关产品推荐

