依赖注入容器有什么好处?

时间:2008-09-25 07:37:20

标签: xml spring dependency-injection

16 个答案:

答案 0 :(得分:40)

对于我自己来说,使用IoC(并使用外部配置)的主要原因之一是围绕以下两个方面:

  • 测试
  • 生产维护

<强>测试

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

  1. 单元测试
  2. 集成测试
  3. 黑盒测试
  4. 您要做的是最后两个测试场景(Integration&amp; Black box),不重新编译应用程序的任何部分。

    如果您的任何测试方案要求您更改配置(即:使用另一个组件来模拟银行业务集成,或执行性能负载),这可以轻松处理(这确实有利于配置DI虽然是IoC的一面。

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

    <强>生产

    作为开发人员,您不会(也不应该)控制生产环境(特别是当您的应用程序被分发到多个客户或单独的站点时),这对我来说是使用IoC的真正好处和外部配置,因为由基础设施/生产支持来调整和调整实时环境,而不必回到开发人员和测试(当他们想要做的就是移动组件时,成本更高)。

    <强>摘要

    IoC外部配置的主要好处在于为其他人(非开发人员)提供配置应用程序的能力,根据我的经验,这仅在有限的情况下有用:

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

    在实践中,我发现即使在开发一些你可以控制环境的东西时它也会被运行,随着时间的推移,最好让其他人有能力改变配置:

    • 开发时你不知道它什么时候会改变(应用程序是如此有用,你的公司将它出售给其他人)。
    • 我不想在每次请求稍微更改时更改代码,而这些更改可以通过设置和使用良好的配置模型来处理。

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

答案 1 :(得分:14)

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

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

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

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

    问题中提供的示例仅涉及正确分解的DI对象库可以提供的表达能力的表面。通过一些练习和大量自律,大多数DI从业者发现他们可以构建具有100%应用程序代码测试覆盖率的系统。仅这一点就是非同寻常的。这不是对几百行代码的小应用程序的100%测试覆盖率,而是包含数十万行代码的应用程序的100%测试覆盖率。我无法描述提供此级别可测试性的任何其他设计模式。

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

    简而言之,基于DI的大规模应用程序更易于调试且更易于理解。虽然Xml配置不是“编译时检查”,但是如果作者试图将具有不兼容接口的对象注入另一个对象,则该作者知道的所有应用程序服务都将向开发人员提供错误消息。大多数都提供“检查”功能,涵盖所有已知对象配置。通过检查要注入的对象A是否为所有已配置的对象注入实现对象B所需的接口,可以轻松快速地完成此操作。

答案 2 :(得分:7)

这是一个有点问题的问题,但我倾向于同意大量的xml配置并没有太大的好处。我喜欢我的应用程序尽可能地依赖于依赖关系,包括大量的框架。

他们很多时候都会简化代码,但是它们也有复杂的开销,这使得追踪问题变得相当困难(我亲眼看到了这些问题,直接的Java我会更舒服地处理)

我认为这取决于风格,以及你感到满意的......你喜欢自己动手解决方案,并且有利于内部了解它,或者依靠现有的解决方案来解决这个问题。配置不正确吗?这都是一种权衡。

但是,XML配置对我来说有点小麻烦......我会不惜一切代价避免它。

答案 3 :(得分:5)

只要您将代码更改为数据,就可以朝着正确的方向迈出一步。

将任何内容编码为数据意味着您的代码本身更通用且可重用。这也意味着您的数据可以用恰好适合它的语言指定。

此外,XML文件可以读入GUI或其他工具,并且可以通过实际操作轻松操作。你会如何使用代码示例?

我一直在考虑大多数人将代码转化为数据的事情,它使得代码更加清晰。我发现不可思议的是,人们会在代码而不是数据中创建一个菜单 - 很明显,由于样板文件,在代码中执行它是完全错误的。

答案 4 :(得分:3)

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

例如,Spring允许您实现InitializingBean接口并添加afterPropertiesSet方法(您还可以指定“init-method”以避免将代码耦合到Spring)。这些方法将允许您确保在启动时正确配置了作为类实例中的字段指定的任何接口,然后您不再需要对getter和setter进行空值检查(假设您允许单例保持线程安全) )。

此外,使用DI容器进行复杂的初始化要轻松得多,而不是自己做。例如,我协助使用XFire(不是CeltiXFire,我们只使用Java 1.4)。该应用程序使用Spring,但遗憾的是它使用了XFire的services.xml配置机制。当一个元素集合需要声明它有ZERO或更多实例而不是一个或多个实例时,我不得不覆盖这个特定服务的一些提供的XFire代码。

在其Spring bean架构中定义了某些XFire默认值。因此,如果我们使用Spring来配置服务,则可以使用bean。相反,发生的事情是我必须在services.xml文件中提供特定类的实例,而不是使用bean。为此,我需要提供构造函数并设置在XFire配置中声明的引用。我需要做的真正改变需要我重载一个类。

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

<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" />

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

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容器中已经提供了属性并且您只需更改它们以适应特殊情况时,处理代码中的怪癖会更加清晰,这种情况经常发生。

答案 5 :(得分:3)

我有你的答案

每种方法都有明显的权衡,但外化的XML配置文件对于企业开发非常有用,在这种开发中,构建系统用于编译代码而不是IDE。使用构建系统,您可能希望在代码中注入某些值 - 例如构建版本(每次编译时都必须手动更新)。当您的构建系统从某个版本控制系统中删除代码时,痛苦会更大。在编译时修改简单值将要求您更改文件,提交,编译,然后每次更改时还原。这些不是您要提交到版本控件中的更改。

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

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

更新: 以上所有示例都是关于不一定需要依赖类的事情。但是,您可以轻松构建需要复杂对象和自动化的案例 - 例如:

  • 想象一下,您有一个监控网站流量的系统。根据并发用户的数量,它会打开/关闭日志记录机制。也许当机制关闭时,存根对象就会被放置。
  • 想象一下,您有一个网络会议系统,根据用户数量,您希望根据参与者数量切换出P2P的能力

答案 6 :(得分:2)

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

答案 7 :(得分:2)

您可以为女朋友插入新的实施方案。因此可以在不重新编译代码的情况下注入新的女性。

<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>

(以上假设Female和HotFemale实现相同的GirlfFriend界面)

答案 8 :(得分:1)

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

例如,

StructureMap和Ninject使用流畅的接口来配置容器。您不再受限于使用XML配置文件。 Spring也存在于.NET中,它很大程度上依赖于XML文件,因为它是他历史上的主要配置界面,但仍然可以通过编程方式配置容器。

答案 9 :(得分:1)

轻松将部分配置组合成最终的完整配置。

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

  UI-context.xml
  Model-context.xml
  Controller-context.xml

或者使用不同的UI和一些额外的控制器加载:

  AlternateUI-context.xml
  Model-context.xml
  Controller-context.xml
  ControllerAdditions-context.xml

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

答案 10 :(得分:1)

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

在生产系统中,将一些设置子集(例如,您的例子中的年龄)提取到XML文件并允许例如系统管理员或支持个人更改值而不给予他们对源代码或其他设置的全部权力 - 或者只是为了将它们与复杂性隔离开来。

答案 11 :(得分:1)

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

首先,XML配置不是定义配置的唯一方法。大多数事情都可以使用注释进行配置,而必须使用XML完成的事情是对您不编写的代码的配置,例如您从库中使用的连接池。 Spring 3包含一种使用Java定义DI配置的方法,类似于示例中的手动DI配置。所以使用Spring并不意味着你必须使用基于XML的配置文件。

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

但我确实看到了你的观点,在Java中定义依赖注入配置没有任何问题。我通常在单元测试中自己这样做,当我正在开展一个足够小的项目时,我还没有添加DI框架。我通常不会在Java中指定配置,因为对我而言,当我选择使用Spring时,我正试图摆脱写作的那种管道代码。但这是一个偏好,但这并不意味着XML配置优于基于Java的配置。

答案 12 :(得分:0)

Spring也有一个属性加载器。我们使用此方法设置依赖于环境的变量(例如开发,测试,验收,生产......)。这可以是例如要收听的队列。

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

答案 13 :(得分:0)

您的情况非常简单,因此不需要像Spring那样的IoC(Inversion of Control)容器。另一方面,当你“编程接口而非实现”(这是OOP中的一个好习惯)时,你可以得到这样的代码:

IService myService;
// ...
public void doSomething() {
  myService.fetchData();
}

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

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

答案 14 :(得分:0)

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

答案 15 :(得分:-2)

最吸引人的原因之一是“Hollywood principle”:不要打电话给我们,我们会打电话给你。组件不需要对其他组件和服务本身进行查找;相反,它们会自动提供给它。在Java中,这意味着不再需要在组件内部进行JNDI查找。

单独对组件进行单元测试也更容易:不是给它实际需要的组件实现,而是简单地使用(可能是自动生成的)模拟。