温莎城堡 UsingFactoryMethod with LifestyleTransient

Castle Windsor UsingFactoryMethod with LifestyleTransient

考虑到 castle windsor 3.4.0 中的以下实现:

public class ExampleInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<FailoverDatabaseConnectionExecutor>()
        .ImplementedBy<FailoverDatabaseConnectionExecutor>()
        .LifestyleTransient());

        container.Register(Component.For<DatabaseConnectionExecutor>()
        .ImplementedBy<DatabaseConnectionExecutor>()
        .LifestyleTransient());

        container.Register(Component.For<IDatabaseConnectionExecutor>()
        UsingFactoryMethod(CreateDatabaseConnectionExecutor)
        .LifestyleTransient()
        .IsDefault());
    }

    private static IDatabaseConnectionExecutor CreateDatabaseConnectionExecutor(IKernel kernel)
    {
        var configurationRepository = kernel.Resolve<IConfigurationRepository>();

        return configurationRepository.GetSetting(ConfigurationSettings.DatabaseFailoverEnabled, () => false)
            ? (IDatabaseConnectionExecutor)kernel.Resolve<FailoverDatabaseConnectionExecutor>()
            : kernel.Resolve<DatabaseConnectionExecutor>();
    }
}

框架返回以下异常:

Instance FailoverDatabaseConnectionExecutor of component Late bound IDatabaseConnectionExecutor is already being tracked. The factory method providing instances of the component is reusing instances, but the lifestyle of the component is Transient which requires new instance each time. In most cases it is advised for the factory method not to be handling reuse of the instances, but to chose a lifestyle that does that appropriately. Alternatively, if you do not wish for Windsor to track the objects coming from the factory change your regustration to '.UsingFactoryMethod(yourFactory, managedExternally: true)'

这导致依赖链无法解析,并且在我们的控制器上的 属性 注入中出现空值。

我们想要实现的是根据配置值 ConfigurationSettings.DatabaseFailoverEnabled 来切换分辨率。我们希望这在工厂和底层解析类型上以瞬态方式发生。

从错误来看这似乎是不可能的,我们的问题是我们如何实现工厂风格的实现,同时仍然在 FailoverDatabaseConnectionExecutorDatabaseConnectionExecutor[=16= 上保持短暂的生命周期]

编辑:

在花了一些时间进一步调查之后,我的依赖链中的一个对象似乎是一个问题。当其中一个对象实现 IDisposable 时,与 UsingFactoryMethod 一起使用时会发生此错误。

考虑以下(简化的)示例:

public class ServiceInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Component.For<ISomeConnectionService>()
                .ImplementedBy<SomeConnectionService>()
                .LifestyleTransient()
                .IsDefault());

        container.Register(
            Component.For<FailoverDatabaseConnectionExecutor>()
            .ImplementedBy<FailoverDatabaseConnectionExecutor>()
            .LifestyleTransient());

        container.Register(Component.For<IDatabaseConnectionExecutor>()
            .UsingFactoryMethod(CreateDatabaseConnectionExecutor)
            .LifestyleTransient()
            .IsDefault());
    }

    private static IDatabaseConnectionExecutor CreateDatabaseConnectionExecutor(IKernel kernel)
    {
        return kernel.Resolve<FailoverDatabaseConnectionExecutor>();
    }
}

public interface IDatabaseConnectionExecutor
{
}

public class SomeConnectionService : ISomeConnectionService, IDisposable
{
    public SomeConnectionService()
    {

    }

    public void Dispose()
    {
    }
}

public interface ISomeConnectionService
{
}

public class FailoverDatabaseConnectionExecutor : IDatabaseConnectionExecutor
{
    private readonly ISomeConnectionService _someConnectionService;

    public FailoverDatabaseConnectionExecutor(ISomeConnectionService someConnectionService)
    {
        _someConnectionService = someConnectionService;
    }
}

从 SomeConnectionService 中删除 IDisposable 将正确注入依赖链。

有谁知道温莎城堡为什么会这样?

这里的问题是 .UsingFactoryMethod() returns 一个已经注册的组件然后尝试再次注册它。

您可以尝试将 .Named() 添加到您的第一次和第二次注册中。

 container.Register(Component.For<IDatabaseConnectionExecutor>()
    .ImplementedBy<FailoverDatabaseConnectionExecutor>()
    .Named('FailoverExecutor')
    .LifestyleTransient());

 container.Register(Component.For<IDatabaseConnectionExecutor>()
    .ImplementedBy<DatabaseConnectionExecutor>()
    .Named('NormalExecutor')
    .LifestyleTransient());

 container.Register(Component.For<IDatabaseConnectionExecutor>()
    UsingFactoryMethod(CreateDatabaseConnectionExecutor)
    .LifestyleTransient()
    .IsDefault());

然后在您的工厂方法中使用该名称解析组件。

 return configurationRepository.GetSetting(ConfigurationSettings.DatabaseFailoverEnabled, () => false)
        ? kernel.Resolve<IDatabaseConnectionExecutor>("FailoverExecutor")
        : kernel.Resolve<IDatabaseConnectionExecutor>("NormalExecutor");

在研究了一些温莎城堡代码后,很明显在跟踪标记为退役的对象方面存在问题。 More details can be found here

我们通过改用 IHandlerSelector 克服了这个问题。这允许我们根据配置设置在 运行 时间动态 select 依赖项。

public class RepositoriesInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<IDatabaseConnectionExecutor>()
            .ImplementedBy<FailoverDatabaseConnectionExecutor>()

            .LifestyleTransient());

        container.Register(Component.For<IDatabaseConnectionExecutor>()
            .ImplementedBy<DatabaseConnectionExecutor>()
            .LifestyleTransient());

        var configuration = container.Resolve<IConfigurationRepository>();

        container.Kernel.AddHandlerSelector(new DatabaseExecutorHandlerSelector(configuration));

        container.Release(configuration);
    }
}

public class DatabaseExecutorHandlerSelector : IHandlerSelector
{
    private readonly IConfigurationRepository configurationRepository;

    public DatabaseExecutorHandlerSelector(IConfigurationRepository configurationRepository)
    {
        this.configurationRepository = configurationRepository;
    }

    public bool HasOpinionAbout(string key, Type service)
    {
        return service == typeof(IDatabaseConnectionExecutor);
    }

    public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
    {
        var failoverEnabled = configurationRepository.GetSetting(ConfigurationSettings.BagDatabaseFailoverEnabled, () => false);

        var implementationToUse = failoverEnabled ? 
            typeof(FailoverDatabaseConnectionExecutor) : 
            typeof(DatabaseConnectionExecutor);

        return handlers.First(x => x.ComponentModel.Implementation == implementationToUse);
    }
}