Castle Windsor 显式共享依赖项
Castle Windsor explicitly sharing dependencies
如何在不同时间执行的组件图之间共享临时对象?
我有一个来自旧遗留代码的状态引擎。每个状态都由一个 IState 表示,并负责在流程中创建下一个状态。
public interface IState
{
Guid Session { get; }
IState GetNextState();
}
起始状态由模型初始化:
public class Model
{
private readonly IStateFactory _stateFactory;
public Model(IStateFactory stateFactory)
{
_stateFactory = stateFactory;
}
public IState GetFirstState()
{
return _stateFactory.GetStateA();
}
}
每个状态都包含一个会话上下文(这里简化为只包含一个 GUID)。
public class Context : IDisposable
{
public static int CreatedCount = 0;
public static int DisposedCount = 0;
//Has other DI injected dependencies.
public Context()
{
CreatedCount++;
}
public Guid SessionGuid { get; } = Guid.NewGuid();
public void Dispose()
{
DisposedCount++;
}
}
已添加 "CreatedCount" 和 "DisposedCount" 以帮助演示问题。请注意,它们是静态整数。
状态的实现可能是这样的:
public class MyState : IState
{
private readonly Context _context;
private readonly IStateFactory _stateFactory;
public MyState(IStateFactory stateFactory, Context context)
{
_context = context;
_stateFactory = stateFactory;
}
public Guid Session => _context.SessionGuid;
public IState GetNextState()
{
var nextState = _stateFactory.GetStateB(_context);
_stateFactory.DestroyState(this);
return nextState;
}
}
状态工厂是一个简单的 Castle Windsor 实现的 TypedFactory 接口。
public interface IStateFactory
{
IState GetFirstState();
IState GetStateB(Context context);
void DestroyState(IState state);
}
想法是每个 "state" 都可以根据某个动作启动下一个状态,并且当前状态 "context" 应该在下一个状态中使用。
容器以预期方式构建:
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<Context>().LifestyleTransient(),
Component.For<IState>().ImplementedBy<MyState>().Named("stateA").LifestyleTransient(),
Component.For<IState>().ImplementedBy<MyState>().Named("stateB").LifestyleTransient(),
Component.For<IStateFactory>().AsFactory()
);
本质上,我希望 "stateB" 获得 Context 的所有权。但是当我释放 "stateA"(通过调用 MyState.GetNextState)时,上下文被释放并释放!我如何告诉 Castle.Windsor 将所有权转移到下一个州?
var model = container.Resolve<Model>();
var initialState = model.GetFirstState();
var nextState = initialState.GetNextState(); //releases the initial State.
Assert.That(initialState.Session, Is.EqualTo(nextState.Session)); //The context was 'shared' by stateA by passing it into the factory method for stateB.
Assert.That(Context.CreatedCount, Is.EqualTo(1));
Assert.That(Context.DisposedCount, Is.EqualTo(0)); //FAIL! Castle Windsor should see that the shared "Context" was passed into the constructor of modelB, and added a reference to it.
container.Release(model);
container.Release(nextState); //usually done by the model.
Assert.That(Context.CreatedCount, Is.EqualTo(1));
Assert.That(Context.DisposedCount, Is.EqualTo(1));
需要注意的是,状态转换可以从另一个线程启动,但在创建线程上调用。这会扰乱默认的 Castle Windsor 作用域生活方式使用的 CallContext。这是针对桌面应用程序的,因此默认的 WCF 和 Web 请求范围生活方式不适用。
使用LifestyleScoped
:
Component.For<Context>().LifestyleScoped()
using (container.BeginScope())
{
var model = container.Resolve<Model>();
var initialState = model.GetFirstState();
var nextState = initialState.GetNextState();
Assert.That(Context.CreatedCount, Is.EqualTo(0));
}
Assert.That(Context.CreatedCount, Is.EqualTo(1));
我想出了另一个解决这个问题的方法,它适用于 Castle Windsors 范围不起作用的地方。我创造了一种生活方式,只要有东西通过引用计数使用它,它就会一直存在。一旦引用计数变为 0,对象就会被释放。
public class ReferenceCountedSingleton : AbstractLifestyleManager, IContextLifestyleManager
{
private readonly object _lock = new object();
private Burden _cachedBurden;
private int _referenceCount;
public override void Dispose()
{
var localInstance = _cachedBurden;
if (localInstance != null)
{
localInstance.Release();
_cachedBurden = null;
}
}
public override object Resolve(CreationContext context, IReleasePolicy releasePolicy)
{
lock(_lock)
{
_referenceCount++;
if (_cachedBurden != null)
{
Debug.Assert(_referenceCount > 0);
return _cachedBurden.Instance;
}
if (_cachedBurden != null)
{
return _cachedBurden.Instance;
}
var burden = CreateInstance(context, false);
_cachedBurden = burden;
Track(burden, releasePolicy);
return burden.Instance;
}
}
public override bool Release(object instance)
{
lock (_lock)
{
if (_referenceCount > 0) _referenceCount--;
if (_referenceCount > 0) return false;
_referenceCount = 0;
_cachedBurden = null;
return base.Release(instance);
}
}
protected override void Track(Burden burden, IReleasePolicy releasePolicy)
{
burden.RequiresDecommission = true;
base.Track(burden, releasePolicy);
}
public object GetContextInstance(CreationContext context)
{
return context.GetContextualProperty(ComponentActivator);
}
}
它是这样使用的:
container.Register(Component.For<Context>).LifestyleCustom<ReferenceCountedSingleton>());
如何在不同时间执行的组件图之间共享临时对象?
我有一个来自旧遗留代码的状态引擎。每个状态都由一个 IState 表示,并负责在流程中创建下一个状态。
public interface IState
{
Guid Session { get; }
IState GetNextState();
}
起始状态由模型初始化:
public class Model
{
private readonly IStateFactory _stateFactory;
public Model(IStateFactory stateFactory)
{
_stateFactory = stateFactory;
}
public IState GetFirstState()
{
return _stateFactory.GetStateA();
}
}
每个状态都包含一个会话上下文(这里简化为只包含一个 GUID)。
public class Context : IDisposable
{
public static int CreatedCount = 0;
public static int DisposedCount = 0;
//Has other DI injected dependencies.
public Context()
{
CreatedCount++;
}
public Guid SessionGuid { get; } = Guid.NewGuid();
public void Dispose()
{
DisposedCount++;
}
}
已添加 "CreatedCount" 和 "DisposedCount" 以帮助演示问题。请注意,它们是静态整数。
状态的实现可能是这样的:
public class MyState : IState
{
private readonly Context _context;
private readonly IStateFactory _stateFactory;
public MyState(IStateFactory stateFactory, Context context)
{
_context = context;
_stateFactory = stateFactory;
}
public Guid Session => _context.SessionGuid;
public IState GetNextState()
{
var nextState = _stateFactory.GetStateB(_context);
_stateFactory.DestroyState(this);
return nextState;
}
}
状态工厂是一个简单的 Castle Windsor 实现的 TypedFactory 接口。
public interface IStateFactory
{
IState GetFirstState();
IState GetStateB(Context context);
void DestroyState(IState state);
}
想法是每个 "state" 都可以根据某个动作启动下一个状态,并且当前状态 "context" 应该在下一个状态中使用。
容器以预期方式构建:
var container = new WindsorContainer();
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<Context>().LifestyleTransient(),
Component.For<IState>().ImplementedBy<MyState>().Named("stateA").LifestyleTransient(),
Component.For<IState>().ImplementedBy<MyState>().Named("stateB").LifestyleTransient(),
Component.For<IStateFactory>().AsFactory()
);
本质上,我希望 "stateB" 获得 Context 的所有权。但是当我释放 "stateA"(通过调用 MyState.GetNextState)时,上下文被释放并释放!我如何告诉 Castle.Windsor 将所有权转移到下一个州?
var model = container.Resolve<Model>();
var initialState = model.GetFirstState();
var nextState = initialState.GetNextState(); //releases the initial State.
Assert.That(initialState.Session, Is.EqualTo(nextState.Session)); //The context was 'shared' by stateA by passing it into the factory method for stateB.
Assert.That(Context.CreatedCount, Is.EqualTo(1));
Assert.That(Context.DisposedCount, Is.EqualTo(0)); //FAIL! Castle Windsor should see that the shared "Context" was passed into the constructor of modelB, and added a reference to it.
container.Release(model);
container.Release(nextState); //usually done by the model.
Assert.That(Context.CreatedCount, Is.EqualTo(1));
Assert.That(Context.DisposedCount, Is.EqualTo(1));
需要注意的是,状态转换可以从另一个线程启动,但在创建线程上调用。这会扰乱默认的 Castle Windsor 作用域生活方式使用的 CallContext。这是针对桌面应用程序的,因此默认的 WCF 和 Web 请求范围生活方式不适用。
使用LifestyleScoped
:
Component.For<Context>().LifestyleScoped()
using (container.BeginScope())
{
var model = container.Resolve<Model>();
var initialState = model.GetFirstState();
var nextState = initialState.GetNextState();
Assert.That(Context.CreatedCount, Is.EqualTo(0));
}
Assert.That(Context.CreatedCount, Is.EqualTo(1));
我想出了另一个解决这个问题的方法,它适用于 Castle Windsors 范围不起作用的地方。我创造了一种生活方式,只要有东西通过引用计数使用它,它就会一直存在。一旦引用计数变为 0,对象就会被释放。
public class ReferenceCountedSingleton : AbstractLifestyleManager, IContextLifestyleManager
{
private readonly object _lock = new object();
private Burden _cachedBurden;
private int _referenceCount;
public override void Dispose()
{
var localInstance = _cachedBurden;
if (localInstance != null)
{
localInstance.Release();
_cachedBurden = null;
}
}
public override object Resolve(CreationContext context, IReleasePolicy releasePolicy)
{
lock(_lock)
{
_referenceCount++;
if (_cachedBurden != null)
{
Debug.Assert(_referenceCount > 0);
return _cachedBurden.Instance;
}
if (_cachedBurden != null)
{
return _cachedBurden.Instance;
}
var burden = CreateInstance(context, false);
_cachedBurden = burden;
Track(burden, releasePolicy);
return burden.Instance;
}
}
public override bool Release(object instance)
{
lock (_lock)
{
if (_referenceCount > 0) _referenceCount--;
if (_referenceCount > 0) return false;
_referenceCount = 0;
_cachedBurden = null;
return base.Release(instance);
}
}
protected override void Track(Burden burden, IReleasePolicy releasePolicy)
{
burden.RequiresDecommission = true;
base.Track(burden, releasePolicy);
}
public object GetContextInstance(CreationContext context)
{
return context.GetContextualProperty(ComponentActivator);
}
}
它是这样使用的:
container.Register(Component.For<Context>).LifestyleCustom<ReferenceCountedSingleton>());