关于xml:依赖注入容器有什么好处?

What are the benefits of dependency injection containers?

我了解依赖注入本身的好处。以春天为例。我还了解其他Spring特性的好处,如AOP、不同类型的助手等。我只是想知道,XML配置的好处是什么,例如:

1
2
3
4
5
6
<bean id="Mary" class="foo.bar.Female">
  <property name="age" value="23"/>
</bean>
<bean id="John" class="foo.bar.Male">
  <property name="girlfriend" ref="Mary"/>
</bean>

与普通Java代码相比,如:

1
2
3
4
Female mary = new Female();
mary.setAge(23);
Male john = new Male();
john.setGirlfriend(mary);

这是更容易调试,编译时间检查,可以理解的人谁只知道爪哇。那么依赖注入框架的主要目的是什么呢?(或显示其优点的代码。)

更新:
万一

1
2
3
4
IService myService;// ...
public void doSomething() {  
  myService.fetchData();
}

如果有多个实现,IOC框架如何猜测我希望注入哪一个MyService实现?如果给定接口只有一个实现,并且我让IOC容器自动决定使用它,那么在第二个实现出现后,它将被破坏。如果一个接口只有一个可能的实现,那么您不需要注入它。

看到IOC的一小部分配置显示了它的好处,这将是非常有趣的。我用Spring已经有一段时间了,我不能提供这样的例子。我可以展示单行,演示Hibernate、DWR和我使用的其他框架的好处。

更新2:
我认识到IOC配置可以在不重新编译的情况下进行更改。这真的是个好主意吗?我可以理解,当有人想在不重新编译的情况下更改数据库凭据时,他可能不是开发人员。在您的实践中,开发人员以外的其他人更改IOC配置的频率是多少?我认为对于开发人员来说,不需要改变配置就可以重新编译这个特定的类。对于非开发人员,您可能希望让他的生活更轻松,并提供一些更简单的配置文件。

更新3:

External configuration of mapping between interfaces and their concrete implementations

什么能使它扩展?您不需要将所有代码都设置为外部的,虽然您确实可以—只需将其放在classname.java.txt文件中,然后快速地手动读取和编译—哇,您避免了重新编译。为什么要避免编译?!

You save coding time because you provide mappings declaratively, not in a procedural code

我理解有时候声明性方法节省时间。例如,我只声明一次bean属性和db列之间的映射,Hibernate在加载、保存、基于hsql构建SQL等过程中使用此映射。这就是声明性方法的工作之处。在Spring的情况下(在我的示例中),声明有更多的行,并且具有与相应代码相同的表达性。如果有这样一个声明短于代码的例子,我想看看。

Inversion of Control principle allows for easy unit testing because you can replace real implementations with fake ones (like replacing SQL database with an in-memory one)

我确实理解控制反转的好处(我更喜欢把这里讨论的设计模式称为依赖注入,因为IOC更一般——有很多种控制,我们只反转其中一种——初始化控制)。我在问为什么有人需要编程语言以外的东西。我绝对可以用代码来代替真正的实现。这段代码将表示与配置相同的东西——它只是用假值初始化字段。

1
mary = new FakeFemale();

我确实了解DI的好处。我不理解与配置相同的代码相比,外部XML配置增加了哪些好处。我不认为应该避免编译——我每天都编译,现在还活着。我认为DI的配置是声明性方法的坏例子。声明如果声明一次,并以不同的方式多次使用,比如Hibernate CFG,其中bean属性和db列之间的映射用于保存、加载、构建搜索查询等。Spring DI配置可以很容易地转换为配置代码,就像在这个问题的开头一样,不是吗?它只用于bean初始化,不是吗?这意味着声明性方法在这里不添加任何内容,是吗?

当我声明Hibernate映射时,我只是给Hibernate一些信息,它基于这个信息工作——我不告诉它要做什么。在Spring的情况下,我的声明告诉Spring应该做什么——那么为什么要声明它,为什么不直接做呢?

上次更新:
伙计们,很多答案告诉我依赖注入,我知道这很好。问题是DI配置的目的,而不是初始化代码——我倾向于认为初始化代码更短更清晰。到目前为止,我得到的唯一答案是,当配置更改时,它避免了重新编译。我想我应该发布另一个问题,因为这是我的一个大秘密,为什么在这种情况下应该避免编译。


对我来说,使用IOC(并利用外部配置)的主要原因之一是以下两个方面:

  • 测试
  • 生产维护

测试

如果您将测试分为3个场景(这在大规模开发中相当正常):

  • 单元测试
  • 集成测试
  • 黑盒测试
  • 您将要做的是,对于最后两个测试场景(集成和黑盒),不重新编译应用程序的任何部分。

    如果您的任何一个测试场景要求您更改配置(即:使用另一个组件来模拟银行集成,或执行性能负载),则可以很容易地处理这一问题(尽管这是在配置IOC的DI端的好处下进行的)。

    此外,如果您的应用程序在多个站点(具有不同的服务器和组件配置)使用,或者在实时环境中具有更改的配置,则可以使用稍后的测试阶段来验证应用程序将处理这些更改。

    生产

    作为一名开发人员,您不(也不应该)控制生产环境(尤其是当您的应用程序被分发到多个客户或单独的站点时),这对我来说是使用IOC和外部配置的真正好处,因为这取决于基础设施/生产支持来调整和调整实时环境t无需回到开发人员那里并通过测试(当他们只想移动一个组件时,成本会更高)。

    总结

    从我的经验来看,IOC的外部配置的主要好处在于赋予他人(非开发人员)配置您的应用程序的能力,这仅在有限的一组情况下有用:

    • 应用程序分发到多个环境不同的站点/客户端。
    • 生产环境和设置的有限开发控制/输入。
    • 测试场景。

    在实践中,我发现,即使在开发您确实可以控制环境的东西时,随着时间的推移,最好给其他人更改配置的能力:

    • 当你开发的时候,你不知道它什么时候会改变(这个应用程序非常有用,你的公司把它卖给别人)。
    • 我不想每次请求一个微小的更改时都要修改代码,而这个更改可以通过设置和使用一个好的配置模型来处理。

    注意:应用程序指的是完整的解决方案(不仅仅是可执行的),所以应用程序运行所需的所有文件。


    依赖注入是一种编码样式,其根源在于这样的观察:对象委托通常是比对象继承更有用的设计模式(即,对象有关系比对象是关系更有用)。然而,DI工作所必需的另一个要素是创建对象接口。结合这两种强大的设计模式,软件工程师很快意识到他们可以创建灵活的松散耦合的代码,从而产生依赖注入的概念。然而,直到对象反射在某些高级语言中变得可用,DI才真正起飞。反射组件是当今大多数DI系统的核心,因为DI真正酷的方面需要能够以编程方式选择对象,并使用外部独立于对象本身的系统配置和注入到其他对象中。好的。

    语言必须为正常的面向对象编程技术提供良好的支持,以及对对象接口和对象反射的支持(例如Java和C语言)。虽然您可以在C++系统中使用DI模式来构建程序,但在适当的语言中缺乏反射支持却阻止了它支持应用服务器和其他DI平台,从而限制了DI模式的表达能力。好的。

    使用DI模式构建的系统的优势:好的。

  • DI代码更容易重用,因为"依赖"功能被外推到定义良好的接口中,从而允许由适当的应用程序平台处理其配置的独立对象随意插入其他对象。
  • DI代码更容易测试。通过构建实现应用程序逻辑期望的接口的"模拟"对象,可以在黑盒中测试对象所表示的功能。
  • DI代码更灵活。它本质上是松散耦合的代码——到了一个极端。这允许程序员根据一端所需的接口和另一端所表示的接口来选择对象的连接方式。
  • DI对象的外部(XML)配置意味着其他人可以按照不可预见的方向定制代码。
  • 外部配置也是关注点模式的分离,因为对象初始化和对象依赖性管理的所有问题都可以由应用服务器处理。
  • 注意,使用DI模式不需要外部配置,对于简单的互连,一个小的构建器对象通常是足够的。两者在灵活性上存在着权衡。构建器对象不像外部可见的配置文件那样灵活。DI系统的开发人员必须权衡灵活性优于便利性的优势,注意配置文件中表示的对对象结构的小规模、细粒度控制可能会增加混乱和维护成本。
  • 显然,DI代码看起来更麻烦,让所有那些配置对象以注入其他对象的XML文件存在的缺点看起来很困难。然而,这是DI系统的重点。您可以将代码对象作为一系列配置设置进行混合和匹配,从而可以使用第三方代码构建复杂的系统,而您的代码最少。好的。

    问题中提供的示例仅涉及适当分解的DI对象库可以提供的表达能力的表面。通过一些实践和许多自律,大多数DI从业者发现他们可以构建应用程序代码100%测试覆盖率的系统。这一点本身就很特别。这不是几百行代码的小应用程序的100%测试覆盖率,而是包含数十万行代码的应用程序的100%测试覆盖率。我无法描述提供这种可测试性级别的任何其他设计模式。好的。

    您是正确的,仅仅10行代码的应用程序比几个对象加上一系列XML配置文件更容易理解。然而,与大多数功能强大的设计模式一样,当您继续向系统添加新功能时,您会发现收益。好的。

    总之,大尺度的应用都是基于easier to debug和easier了解。while the configuration is not是XML的编译时间是在应用服务checked that this is the author of developer感知将提供与他们attempt if error messages to inject安安不兼容接口的对象有一个对象"。大多数是在提供和检查对象的特征covers Configurations known that。easily this is done quickly and that the to-模式检查对象是在注入implements the interface for required by对象B的对象injections configured。>

    好吧。


    这是一个有点负荷的问题,但我倾向于同意,大量的XML配置实际上并没有带来多大的好处。我喜欢我的应用程序尽可能轻视依赖关系,包括重要的框架。

    它们在很多时候简化了代码,但是它们也有复杂的开销,这使得跟踪问题变得相当困难(我第一次看到了这样的问题,而直接Java我会更容易处理)。

    我想这有点取决于你的风格,以及你喜欢什么…您是否喜欢驾驶自己的解决方案并从中了解它的好处,或者在配置不正确时依靠现有的解决方案,这些解决方案可能会很困难?这是一种权衡。

    然而,XML配置有点像我的一个宠物。我不惜一切代价来避免它。


    任何时候,只要你能把代码转换成数据,你就会朝着正确的方向迈出一步。

    将任何东西编码为数据意味着代码本身更通用、更可重用。它还意味着您的数据可以用适合它的语言指定。

    此外,XML文件可以被读取到GUI或其他工具中,并且可以很容易地进行实际操作。对于代码示例,您将如何做到这一点?

    我不断地将大多数人将作为代码实现的东西分解成数据,这使得剩下的代码更加干净。我发现人们用代码而不是数据创建菜单是不可想象的——很明显,用代码创建菜单是完全错误的,因为有了样板文件。


    我知道你的答案了

    每个方法都有明显的折衷,但是外部化的XML配置文件对于企业开发很有用,在企业开发中,构建系统用于编译代码,而不是您的IDE。使用构建系统,您可能希望向代码中注入某些值,例如构建的版本(每次编译时手动更新可能会很痛苦)。当您的构建系统从某个版本控制系统中提取代码时,痛苦会更大。在编译时修改简单的值需要您更改一个文件,提交它,编译,然后每次对每个更改进行恢复。这些不是您想要提交到版本控制中的更改。

    关于构建系统和外部配置的其他有用用例:

    • 为不同构建的单个代码库注入样式/样式表
    • 为单个代码库注入不同的动态内容集(或对它们的引用)
    • 为不同的生成/客户端注入本地化上下文
    • 将WebService URI更改为备份服务器(当主服务器关闭时)

    更新:上面所有的例子都是关于那些不需要依赖类的东西。但是,您可以轻松构建需要复杂对象和自动化的案例,例如:

    • 假设你有一个系统,它在其中监控你网站的流量。根据并发用户的不同,它打开/关闭日志记录机制。也许在机制关闭的时候,一个存根对象被放在它的位置上。
    • 假设你有一个网络会议系统,在这个系统中,根据用户的不同,你希望根据参与者的不同,切换出进行P2P的能力。


    使用DI容器的原因是,您不必在代码中预先配置十亿个属性,这些属性只是getter和setter。你真的想用新的x()对所有的代码进行硬编码吗?当然,您可以有一个默认值,但是DI容器允许创建单例,这非常容易,并且允许您关注代码的细节,而不是初始化代码的复杂任务。

    例如,spring允许您实现initializingbean接口并添加afterpropertiesset方法(您还可以指定"init方法"以避免将代码耦合到spring)。这些方法将使您能够确保在启动时正确配置类实例中指定为字段的任何接口,然后您不再需要空检查getter和setter(假设您确实允许singleton保持线程安全)。

    此外,使用DI容器进行复杂的初始化要比自己进行初始化容易得多。例如,我协助使用XFARE(不使用CelixFi火,我们只使用Java 1.4)。该应用程序使用了Spring,但不幸的是它使用了Xfire的services.xml配置机制。当一个元素集合需要声明它有零个或多个实例而不是一个或多个实例时,我必须为这个特定的服务重写一些提供的Xfire代码。

    在其SpringBeans模式中定义了某些Xfire默认值。所以,如果我们使用Spring来配置服务,那么bean可能已经被使用了。相反,我必须在services.xml文件中提供特定类的实例,而不是使用bean。为此,我需要提供构造函数并设置在Xfire配置中声明的引用。我需要做的真正的改变要求我超负荷的上一节课。

    但是,由于services.xml文件的存在,我不得不创建四个新类,根据它们的构造函数中Spring配置文件中的默认值设置它们的默认值。如果我们能够使用弹簧配置,我可以说:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <bean id="base" parent="RootXFireBean">
        <property name="secondProperty" ref="secondBean" />
    </bean>

    <bean id="secondBean" parent="secondaryXFireBean">
        <property name="firstProperty" ref="thirdBean" />
    </bean>

    <bean id="thirdBean" parent="thirdXFireBean">
        <property name="secondProperty" ref="myNewBean" />
    </bean>

    <bean id="myNewBean" class="WowItsActuallyTheCodeThatChanged" />

    相反,它看起来更像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class TheFirstPointlessClass extends SomeXFireClass {
        public TheFirstPointlessClass() {
            setFirstProperty(new TheSecondPointlessClass());
            setSecondProperty(new TheThingThatWasHereBefore());
        }
    }

    public class TheSecondPointlessClass extends YetAnotherXFireClass {
        public TheSecondPointlessClass() {
            setFirstProperty(TheThirdPointlessClass());
        }
    }

    public class TheThirdPointlessClass extends GeeAnotherXFireClass {
        public TheThirdPointlessClass() {
            setFirstProperty(new AnotherThingThatWasHereBefore());
            setSecondProperty(new WowItsActuallyTheCodeThatChanged());
        }
    }

    public class WowItsActuallyTheCodeThatChanged extends TheXFireClassIActuallyCareAbout {
        public WowItsActuallyTheCodeThatChanged() {
        }

        public overrideTheMethod(Object[] arguments) {
            //Do overridden stuff
        }
    }

    因此,最终的结果是,四个附加的、大部分无意义的Java类必须添加到代码库中,以实现对一个附加类和一些简单的依赖容器信息的影响。这不是"证明规则的例外情况",这是规则……当属性已经在DI容器中提供时,代码中的异常处理就更干净了,而您只是简单地更改它们以适应特殊情况,这种情况经常发生。


    您不需要在每次更改配置中的某些内容时重新编译代码。它将简化程序部署和维护。例如,只需在配置文件中更改一次,就可以将一个组件与另一个组件交换。


    您可以为女朋友插入一个新的实现。因此,可以在不重新编译代码的情况下注入新的女性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <bean id="jane" class="foo.bar.HotFemale">
      <property name="age" value="19"/>
    </bean>
    <bean id="mary" class="foo.bar.Female">
      <property name="age" value="23"/>
    </bean>
    <bean id="john" class="foo.bar.Male">
      <property name="girlfriend" ref="jane"/>
    </bean>

    (以上假设雌性和雌性热体实现相同的girlfriend接口)


    在.NET世界中,大多数IOC框架都提供XML和代码配置。

    例如,structuremap和ninject使用fluent接口来配置容器。您不再被限制使用XML配置文件。Spring也存在于.NET中,它严重依赖于XML文件,因为它是其历史上的主配置接口,但仍然可以通过编程方式配置容器。


    易于将部分配置组合成最终完整配置。

    例如,在Web应用程序中,模型、视图和控制器通常在单独的配置文件中指定。使用声明性方法,可以加载,例如:ZZU1〔0〕

    或者加载不同的用户界面和一些额外的控制器:ZZU1〔1〕

    要在代码中执行相同的操作,需要一个用于组合部分配置的基础结构。在代码中不可能做到,但使用IOC框架肯定更容易做到。


    从春天的眼光来看,我可以给你两个答案。

    首先,XML配置不是定义配置的唯一方法。大多数事情都可以使用注释进行配置,而必须使用XML来完成的事情就是配置您无论如何都不会编写的代码,比如从库中使用的连接池。Spring 3包括一种用于定义DI配置的方法,该方法类似于示例中的手动滚动DI配置。因此,使用Spring并不意味着必须使用基于XML的配置文件。

    第二,Spring不仅仅是一个DI框架。它还有许多其他特性,包括事务管理和AOP。SpringXML配置将所有这些概念混合在一起。通常在同一个配置文件中,我指定bean依赖项、事务设置,并添加在后台使用AOP实际处理的会话范围的bean。我发现XML配置提供了一个更好的地方来管理所有这些特性。我也觉得基于注释的配置和XML配置比做基于Java的配置要好得多。

    但我确实看到了您的观点,定义Java中的依赖注入配置并没有错。我通常自己在单元测试中这样做,当我在一个足够小的项目上工作时,我没有添加DI框架。我通常不在Java中指定配置,因为对我来说,这是我选择使用Spring时试图避免编写的那种管道代码。这是一个偏好,但这并不意味着XML配置优于基于Java的配置。


    通常,重要的一点是谁在编写程序后更改配置。对于代码中的配置,您隐式地假定更改它的人具有与原始作者相同的技能和对源代码等的访问权限。

    在生产系统中,将一些设置子集(例如示例中的年龄)提取到XML文件中是非常实际的,并且允许系统管理员或支持人员在不赋予他们对源代码或其他设置的全部权限的情况下更改值,或者只是将它们与复杂性隔离。


    您的案例非常简单,因此不需要像Spring这样的IOC(控制反转)容器。另一方面,当您"编程到接口,而不是实现"(这在OOP中是一个很好的实践)时,您可以使用如下代码:

    1
    2
    3
    4
    5
    IService myService;
    // ...
    public void doSomething() {
      myService.fetchData();
    }

    (请注意,myservice的类型是IService——一个接口,而不是具体的实现)。现在可以方便地让您的IOC容器在初始化期间自动提供正确的iService具体实例——当您有许多接口和许多实现时,手工操作可能很麻烦。IOC容器(依赖注入框架)的主要好处是:

    • 接口之间映射的外部配置及其具体实现
    • IOC容器处理一些棘手的问题,如解决复杂的依赖关系图、管理组件的生命周期等。
    • 您可以节省编码时间,因为您以声明的方式提供映射,而不是以过程代码的方式提供映射
    • 控制原理的倒置允许轻松的单元测试,因为您可以用假实现替换真实的实现(例如用内存中的实现替换SQL数据库)。

    Spring还具有属性加载器。我们使用这种方法来设置依赖于环境的变量(例如开发、测试、验收、生产等)。例如,这可能是要侦听的队列。

    如果没有理由更改属性,也没有理由以这种方式配置它。


    在XML配置文件中初始化将简化您与在其计算机上部署了应用程序的客户机的调试/调整工作。(因为它不需要重新编译+二进制文件替换)


    最吸引人的原因之一是"好莱坞原则":不要打电话给我们,我们会打电话给你。组件不需要对其他组件和服务本身进行查找,而是自动提供给它。在爪哇,这意味着不再需要在组件内部进行JNDI查找。

    单独对一个组件进行单元测试也容易得多:您只需使用(可能是自动生成的)模拟,而不必为它提供所需组件的实际实现。