IoC容器的使用;特别是Windsor

28

我认为这个问题的答案是如此明显,以至于没有人会费心写关于这个的内容,但现在已经很晚了,我真的无法理解。

我一直在研究IoC容器(在这种情况下是Windsor),但是我不知道如何从代码的各个部分与容器进行通信。

我了解DI,我一直在使用Poor Mans DI(空构造函数调用带有默认参数实现的重载注入构造函数)已经有一段时间了,我完全可以看到容器的好处。然而,我缺少一个至关重要的信息:每次需要服务时,您应该如何引用容器?

我要创建单个全局实例然后传递吗?肯定不是!

我知道我应该调用这个:

WindsorContainer container = new WindsorContainer(new XmlInterpreter());

比如,当我想加载我的XML配置时,那么我对容器该怎么做呢?每次创建新的容器之后,是否通过某些内部静态魔法持久保存已加载的配置,或者我必须每次重新加载配置(我猜不是,否则生命周期不能正常工作)。

如果不理解这一点,将会阻止我理解生命周期如何工作,并开始使用一些IoC的神奇功能。

谢谢,

安德鲁

5个回答

24

99%的情况下,每个应用程序只有一个容器实例。通常你会在Application_Start(对于Web应用程序)中初始化它,像这样

之后,真正由容器的消费者来决定。例如,一些框架,如MonorailASP.NET MVC允许您拦截实例的创建(在这种情况下是控制器),因此您只需在容器中注册控制器及其依赖项即可。当您获得请求时,容器会负责将每个控制器与其依赖关系注入。例如,查看此ASP.NET MVC控制器。 在这些框架中,您几乎不需要在类中调用或引用容器,这也是推荐的用法。

其他的框架不像Webforms那样容易让你轻松地进入创建过程,因此你必须采用一些hack方法,比如这个,或者主动拉取所需的依赖项(即显式地调用容器)。要拉取依赖项,请使用一个静态的容器网关,比如这个,或者由maxnk描述的那个。请注意,通过这种方式,您实际上是将容器作为服务定位器使用,这并不像控制反转那样有效地解耦组件(请参见这里这里的区别)。

希望这能消除您的疑虑。


实际上,许多环境确实有某个“全局根”类,如果您寻找它的话。Silverlight和WPF有App类(App.xaml的代码后台),这是一个很好的位置来锚定IOC容器。对于较大的WPF和Silverlight应用程序,您肯定要查看Prism,它提供了大规模结构化工具以及Unity,这是微软的IOC容器。 - Cylon Cat
@Cylon: ASP.NET 也有 Application_Start(),但那并不意味着它会让你拦截对象创建。它只是给你一个设置容器的地方。 - Mauricio Scheffer
ASP.NET WebForms页面的创建可以被拦截,并且通过制作一个PageHandlerFactory并在web.config中注册实现来“构建”DI页面。请参见http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=81#comm。 - Jon Adams
@Mufasa:那不是创建拦截。你正在使用已经构建的实例。例如,你不能代理它。 - Mauricio Scheffer
也许从技术上来说它并不是拦截,但它能完成任务,不是吗?只需在一个 web.config 注册到实现一个方法的小类。当涉及编码标准时,ASP.Net Web Forms 显然不是很纯粹。但是现在许多程序员仍然不得不使用它。希望这将有助于他们保持理智,直到他们有机会摆脱 Web Forms。 - Jon Adams
我希望我知道“像这样”的链接是什么。:( - moswald

3
通常情况下,您希望在整个应用程序的生命周期内仅保留一个实例。我大多数时候是在应用程序启动时初始化容器,然后使用类型化工厂进行容器无关的对象提取。
另一种流行的方法是使用静态类包装容器实例,并使用该静态类访问您的(单例)容器。您可以在Ayende的Rhino.Commons库这里中找到一个示例。然而,这种方法有严重的缺点,应该避免使用。

我大部分时间做的事情是在应用程序启动时初始化容器,然后将其传递给需要它的人。我喜欢这个想法。你认为你能提供一个链接或样本吗? - user295190
1
请查看Windsor的文档。 - Krzysztof Kozmic

1

1

正如其他答案所述,有很多选择,我们需要自己找出在我们的情况下最好的选择。

话虽如此,在我看来,拥有一个全局容器并在整个应用程序中访问它会在某种程度上破坏隔离性,因为现在很多代码都依赖于这个全局类。此外,对于分成几个程序集的应用程序,必须使全局容器对所有这些程序集都可访问。

使用Unity,您实际上可以在构造函数中具有IUnityContainer参数,当您解析类时,容器将自动注入到实例中。这样,对于需要解析其他服务的服务,您传递容器而不是强制类引用外部类。

不确定其他框架如何支持此场景(Windsor将注入IKernel)。


2
对于被分成多个程序集的应用程序,全局容器必须对所有这些程序集都可访问。但是,任何IoC容器的主要目标之一是非侵入性。Windsor和其他容器实现并推荐此目标。 - Mauricio Scheffer
@mausch - 它是如何实现的呢,因为似乎普遍建议使用共享单例容器?或者我对此假设有误?我不明白我的答案与IoC的观点相矛盾在哪里。只是一个人如何使用IoC框架而已,而不是框架本身感到有些侵入性。 - Peter Lillevold
1
是的,有一个单一的容器,但除了启动代码和一些特定的粘合代码(例如ASP.NET MVC ControllerFactory,如http://mvccontrib.googlecode.com/svn/trunk/src/MvcContrib.Castle/WindsorControllerFactory.cs)之外,没有人应该知道它。 - Mauricio Scheffer
否则,这就不是依赖注入和控制反转,而是服务定位。请参见http://martinfowler.com/articles/injection.html#ServiceLocatorVsDependencyInjection - Mauricio Scheffer
我必须说,我对IoC还很陌生,仍然无法确定容器应该在哪里使用。我猜测每个程序集一个容器? - Razor
@Sir Psycho - 实际的容器应该位于应用程序层面,通常在应用程序引导期间创建和初始化。通过在整个应用程序中一致使用IoC/DI,您只需要在引导期间访问一次容器来创建根实例(主视图、Presenter等),并且容器将负责构建所需的任何依赖项。 - Peter Lillevold

0

我正在使用这个接口的实现:

public interface IResolver
{
    object Resolve(Type type);
    object Resolve(string name);

    T Resolve<T>() where T : class;
    T Resolve<T>(string name) where T : class;
}

实际上是包含在全局静态类中的,例如:

public static class Resolver // : IResolver
{
    private static IResolver _current;

    public static object Resolve(Type type)
    {
        return Current.Resolve(type);
    }

    public static object Resolve(string name)
    {
        return Current.Resolve(name);
    }

    public static T Resolve<T>() where T : class
    {
        return Current.Resolve<T>();
    }

    public static T Resolve<T>(string name) where T : class
    {
        return Current.Resolve<T>(name);
    }

    private static IResolver Current
    {
        get
        {
            if (_current == null)
            {
                _current = new SpringResolver();
            }

            return _current;
        }
    }
}

另外,我正在尝试遵循一个简单的规则 - 尽可能少地使用Resolver类,而是将服务注入到需要这些服务的对象中。


然而(我意识到我来晚了),如果不是将Resolver作为全局静态类,而是将其作为注入参数传递给需要创建自己对象的对象,则它就成为了抽象工厂,这比服务定位器更好,对吧? - Rune Jacobsen

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