如何使用Castle Windsor重写组件?

17

我想在给定的 windsor-container 中重新定义一个(默认)实现。 OverWrite 是为此而存在的吗?不过它似乎不起作用。

container.Register(
                    Component.For<IServiceOperationAuthorization>()
                            .OverWrite()
                            .Instance(_authorization)
                    );

还有别的想法吗?

谢谢, Lars

6个回答

15

你可以非常简单地在需要覆盖默认实现的地方进行此操作。这是我们集成测试的一个示例。现在两种实现都已注册,但您的代码将使用默认实现,也就是您刚刚注册的实现。效果很好,对应用程序的其他部分没有任何影响:

        var sendMailStub = MockRepository.GenerateStub<ISendMail>();
        _container.Register(
            Component
                .For<ISendMail>()
                .Instance(sendMailStub)
                .IsDefault()
            );

1
IsDefault API 在我所引用的 Windsor Castle 程序集中不可用。我正在引用 2.5.1.0 版的 Castle.Core 程序集。我是在引用较旧的程序集吗? - RBT

10

我同意Krzysztof的观点,这通常不是一个好主意...... 但据我所知,OverWrite()并没有覆盖默认组件,它只是覆盖了由属性定义的生命周期(即[Singleton])。

如果您想替换组件,可以使用container.Kernel.RemoveComponent(string key)来删除旧组件,然后注册新组件。

这里有一个例子,其中这种方法是有意义的。


@George:你能详细解释一下吗?只要没有其他组件依赖于试图被删除的组件,它就应该可以正常工作(这是有道理的)。 - Mauricio Scheffer
啊,我明白了,但是如何指定另一种实现呢?我不想删除整个图并重新添加它。 - George Mauer
1
@Mauricio:听到这个消息很不错。另一个建议:添加ReplaceComponent()方法怎么样?或者更简单的是,提供一种改变解决同一服务组件顺序的方法。因此,用户可以直接使用RegisterComponentAsFirstImplementation(),而不是调用RemoveComponent() + RegisterComponent。 - Igor Brejc
@Igor:你可能可以使用IHandlerSelector或自定义命名子系统解决这个问题,我建议不要在容器中添加更多公共方法,以避免臃肿,但你可以在v3中提出建议! https://castle.uservoice.com/forums/38955-windsor-v3 - Mauricio Scheffer
.RemoveComponent() 在 Windsor 3 中已经不存在,但是您可以使用 IsDefault() 来覆盖首次配置的实例。 - skolima
显示剩余2条评论

3
这可能更适合通过使用装饰器来解决容器问题,在那里您可以明确更改装饰器指导调用的实现...尤其是如果您想替换现有组件(即单例)注入的实现,这些可能存在于您应用程序的生命周期中。需要更多关于您正在尝试实现的背景信息。 这里有关于使用Windsor注册装饰器的更多信息。

链接目前为404。 - cly

3

当我在运行集成测试套件时,需要切换组件实现,但无法使用container.Kernel.RemoveComponent()。因此,我最终采用了一个简单的工具来为我处理这个问题。


很遗憾,所有的覆盖必须在注册之前指定。 - George Mauer

1

我实际上找到了一个不错的解决方案,其中我将xml文件用于覆盖,并使用流畅的注册来设置默认值。

流畅的API将impl的全名作为默认键。在运行时,我会覆盖xml配置的id,以模拟流畅API的键约定。

然后,我在侦听Kernel.ComponentRegistered时注册xml配置。

之后,我只添加代码配置中尚未定义服务的xml服务。

(这是一段时间以前的事情,我只是复制粘贴了代码。如果您发现任何问题,我会进行编辑)

IList<Type> unnamedServices = new List<Type>();
IDictionary<string, Type> namedServices = new Dictionary<string, Type>();

ComponentDataDelegate registered = captureRegistrations(unnamedServices, namedServices);

container.Kernel.ComponentRegistered += registered;

// The method that captures the services
private static ComponentDataDelegate captureRegistrations(
    IList<Type> unnamedServices, IDictionary<string, Type> namedServices)
{
        return (key, handler) =>
               {
                   if (handler.ComponentModel.Name == handler.ComponentModel.Implementation.FullName)
                   {
                       unnamedServices.Add(handler.Service);
                   }
                   else
                   {
                       namedServices.Add(key, handler.Service);
                   }
               };
}

在我注册代码中的服务之前,我会检查它们是否已经被注册。我还创建了一个基类,使这个过程更加容易。这是一个应用程序配置:

public class ApplicationConfiguration : WindsorConfigurationSkeleton
{
    internal static WindsorServiceLocator create()
    {
        var container = createWith(null, "components-config.xml", coreServices, caches, roles);
        return new WindsorServiceLocator(container);
    }

    internal static IEnumerable<IRegistration> coreServices()
    {
        yield return Component.For<ISystemClock>()
            .ImplementedBy<PreciseSystemClock>()
            .Parameters(Parameter.ForKey("synchronizePeriodSeconds").Eq("10"))
            .LifeStyle.Singleton;

        yield return Component.For<IMailService>()
            .ImplementedBy<MailQueueService>()
            .LifeStyle.Singleton;
    }

    internal static IEnumerable<IRegistration> caches()
    {
        yield return Component.For<IDataCache<ServiceAttributes>>()
            .ImplementedBy<NoDataCache<ServiceAttributes>>()
            .LifeStyle.Singleton;

        // ....
    }
}

负责连线的基类: (Logging 来自 Commons.Logging)

public class WindsorConfigurationSkeleton
{
    private static readonly ILog _log = LogManager.GetLogger(
        typeof(WindsorConfigurationSkeleton));

    internal static IWindsorContainer createWith(
        IRegistration[] customs, string configFile, params Func<IEnumerable<IRegistration>>[] methods)
    {
        IWindsorContainer container = new WindsorContainer();
        BugFix.Kernel = container.Kernel;

        container.AddFacility("factory.support", new FactorySupportFacility());

        IList<Type> unnamedServices = new List<Type>();
        IDictionary<string, Type> namedServices = new Dictionary<string, Type>();

        ComponentDataDelegate registered = captureRegistrations(unnamedServices, namedServices);

        container.Kernel.ComponentRegistered += registered;

        if (customs != null)
        {
            container.Register(customs);
        }

        if (configFile != null)
        {
            tryAddXmlConfig(container, configFile);
        }

        container.Kernel.ComponentRegistered -= registered;

        if (methods != null && methods.Length > 0)
        {
            container.Register(union(unnamedServices, namedServices, methods));
        }

        return container;
    }

    private static ComponentDataDelegate captureRegistrations(
        IList<Type> unnamedServices, IDictionary<string, Type> namedServices)
    {
        return (key, handler) =>
               {
                   if (handler.ComponentModel.Name == handler.ComponentModel.Implementation.FullName)
                   {
                        var text = unnamedServices.Contains(handler.Service) ? "another" : "default";
                       _log.Info(
                           m => m(
                                    "Registered {2} service for {0} with {1}.",
                                    handler.Service.GetDisplayName(),
                                    handler.ComponentModel.Implementation.GetDisplayName(),
                                    text
                                    ));

                       unnamedServices.Add(handler.Service);
                   }
                   else
                   {
                        var text = namedServices.ContainsKey(key) ? "another" : "default";
                       _log.Info(
                           m => m(
                                    "Registered {3} service {0} with name '{1}' and {2}.",
                                    handler.ComponentModel.Service,
                                    handler.ComponentModel.Name,
                                    handler.ComponentModel.Implementation.GetDisplayName(),
                                    text
                                    ));
                       namedServices.Add(key, handler.Service);
                   }
               };
    }

    protected static void tryAddXmlConfig(IWindsorContainer container, string filename)
    {
        var fi = Resources.GetFileFromResourceHierarchy(typeof(ApplicationContext).Namespace, filename);
        if ( fi == null ) {
            return;
        }
        var configFile = fi.FullName;
        var xd = immitateFluentApiDefaultIdBehaviour(configFile);
        container.Install(Configuration.FromXml(new StaticContentResource(xd.OuterXml)));

    }

    private static XmlDocument immitateFluentApiDefaultIdBehaviour(string configFile)
    {
        var xd = new XmlDocument();
        xd.Load(configFile);

        foreach (
            XmlElement component in
                xd.SelectNodes("/configuration/components/component[@type and (not(@id) or @id = '')]"))
        {
            var type = Type.GetType(component.GetAttribute("type"), true);
            component.SetAttribute("id", type.FullName);
        }

        return xd;
    }

    private static IRegistration[] union(
        IList<Type> unnamed, IDictionary<string, Type> named, params Func<IEnumerable<IRegistration>>[] methods)
    {
        var all = new List<IRegistration>();
        foreach (var method in methods)
        {
            foreach (var registration in method())
            {
                var registrationType = registration.GetType();
                if (registrationType.IsGenericTypeOf(typeof(ComponentRegistration<>)))
                {
                    var componentType = registrationType.GetGenericArgumentsFor(typeof(ComponentRegistration<>))[0];

                    var name = (string)registrationType.GetProperty("Name").GetValue(registration, null);

                    if (name != null)
                    {
                        if (named.ContainsKey(name))
                        {
                            _log.Debug(
                                m => m("Skipped registering default named component {0}.", name));
                            continue;
                        }
                    }
                    else if (unnamed.Contains(componentType))
                    {
                        _log.Debug(
                            m => m("Skipped registering default component for type {0}.", componentType));
                        continue;
                    }

                    all.Add(registration);
                }
                else
                {
                    all.Add(registration);
                }
            }
        }

        return all.ToArray();
    }
}

0

是的,它确实重新定义了服务的默认实现。为什么要这样做?为什么不一开始就注册它,而不是重新定义它呢?

您能提供更多上下文吗?


我有一个XML文件,它覆盖了流畅的连线。但是现在我通过首先读取XML文件并在XML文件重新定义它们时不从代码中添加配置来解决了这个问题。 - Lars Corneliussen
@Lars:你能具体说明一下你是如何做到的吗?我这里也有同样的问题,我有我的核心连线,并希望在不修改核心连线的情况下切换一个实现到另一个实现。 - Marcus
@KrzysztofKozmic - 我是 Windsor 的狂热用户。在我的 BDD 测试中,我尽可能使用真实的代码来连接我的服务,并使用所有的 IoC 代码来进行连接。然而,有时我想用一个模拟类型替换注册,而且我希望尽可能少地更改生产代码来实现这一点。 - Bronumski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接