了解DI框架的必要性

时间:2009-02-01 11:30:50

标签: java spring dependency-injection

这可能是一个天真的问题。我目前正在学习Spring框架和依赖注入。虽然DI的基本原理很容易理解,但为什么你需要一个精心设计的框架来实现它并不是很明显。

请考虑以下事项:

public abstract class Saw
{
    public abstract void cut(String wood);
}

public class HandSaw extends Saw
{
    public void cut(String wood)
    {
        // chop it up
    }
}

public class ChainSaw extends Saw
{
    public void cut(String wood)
    {
        // chop it a lot faster
    }
}

public class SawMill
{
    private Saw saw;

    public void setSaw(Saw saw)
    {
        this.saw = saw;
    }

    public void run(String wood)
    {
        saw.cut("some wood");
    }
}

然后你可以这样做:

Saw saw = new HandSaw();
SawMill sawMill = new SawMill();
sawMill.setSaw(saw);
sawMill.run();

这相当于:

<bean id="saw" class="HandSaw"/>

<bean id="sawMill" class="SawMill">
   <property name="saw" ref="saw"/>
</bean>

加:

ApplicationContext context = new ClassPathXmlApplicationContext("sawmill.xml");
SawMill springSawMill = (SawMill)context.getBean("sawMill");
springSawMill.run();

当然,这是一个受到尊重的例子,并且对于更复杂的对象关系来说,存储XML文件可能比以编程方式编写文件更有效,但是肯定必须有更多它呢?

(我知道Spring框架不止于此,但我正在考虑需要一个DI容器。)

在第一个例子中,改变中间的依赖关系也是微不足道的:

// gotta chop it faster
saw = new ChainSaw();
sawMill.setSaw(saw);
sawMill.run();

12 个答案:

答案 0 :(得分:15)

我有完全相同的问题,答案是这样的:
当然,你可以做你在“那么你可以简单地做......:”中所描述的内容(让我们称之为“A类”)。但是,这会将A类与HandSaw或SawMill类所需的所有依赖关系联系起来。为什么A应该与HandSaw耦合 - 或者,如果你采取更现实的方案,为什么我的业务逻辑应该耦合到DAO层所需的JDBC连接实现?
我之后提出的解决方案是“然后将依赖项更进一步” - 好吧,现在我的视图已经连接到JDBC连接,我应该只处理HTML(或者Swing,选择你的风格)。

由XML(或JavaConfig)配置的DI框架通过让您“获得所需的服务”来解决这个问题。你不关心它是如何初始化的,它需要什么工作 - 你只需要获得服务对象并激活它。

另外,你对“加号:”(你做SawMill springSawMill = (SawMill)context.getBean("sawMill"); springSawMill.run();的地方)有一种误解 - 你不需要从上下文中获取sawMill bean - 应该将sawMill bean注入DI框架的对象(A类)。所以不是...... getBean(...),而是去“sawMill.run()”,而不是关心它来自哪里,谁初始化它以及如何。对于你所关心的一切,它可以直接进入/ dev / null,或测试输出,或真正的CnC引擎......关键是 - 你不在乎。所有你关心的都是你的小A级应该按照合同规定去做 - 激活一个锯木厂。

答案 1 :(得分:15)

依赖注入是implicit parameter passing的简并形式,其目的基本相同,以解决所谓的The Configurations Problem

  

配置问题是   传播运行时首选项   在整个计划中,允许   多个并发配置集   在静态下安全地共存   保证分离。

依赖注入框架弥补了implicit parametersCurried functions的缺失以及该语言中monads的便捷设施。

答案 2 :(得分:12)

Spring有三个同样重要的功能:

  1. 依赖注入
  2. 面向方面的编程
  3. 框架类库,用于帮助持久化,远程处理,Web mvc等。
  4. 我同意,当你将依赖注入与单个调用new进行比较时,很难看到依赖注入的优势。在这种情况下,后者肯定会看起来更简单,因为它只是一行代码。 Spring的配置总是会增加代码行,因此它不是一个成功的论据。

    当您可以从类中进行交叉关注并使用方面以声明方式设置它们时,它开始看起来好多了。与单个“新”调用的比较并不是Spring创建的。

    使用Spring的最佳结果可能是它推荐的习惯用法使用接口,分层和DRY这样的好原则。这真的只是Rod Johnson在他的咨询演出中使用的面向对象最佳实践的升华。他发现随着时间的推移,他建立的代码帮助他为客户提供更好的软件。他总结了他在“Expert 1:1 J2EE”中的经验,最终将代码公开为Spring。

    我想说,在你认为他的经验可以帮助你编写更好的代码的程度上购买框架。

    在你将所有这三个功能结合起来之前,我认为你无法获得Spring的全部价值。

答案 3 :(得分:6)

  

当然,这是一个受到尊重的例子,并且对于更复杂的对象关系来说,存储XML文件可能比以编程方式编写文件更有效,但是肯定必须有更多它呢?

我认为将“接线”放在配置文件中而不是在代码中手动执行更有意义,原因如下:

  1. 配置在您的代码外部。
  2. 对连接的更改(告诉您的sawmill使用Saw的另一个实例)可以简单地对外部(XML)文件进行更改,不需要更改代码,重新编译,重新部署等。
  3. 当你有几十个类和几个注入层时(例如:你有一个web Controller类,它获得一个包含你的业务逻辑的Service类,它使用{{1从数据库中获取DAO来获取Saw注入其中,等等),手动连接协作者是繁琐的,需要几十行代码,除了接线之外什么都不做起来。
  4. 这有点不那么明确的“好处”,但是通过在代码外部进行所有“连线”,我认为它有助于向开发人员重新强调依赖注入的核心思想,特别是编码到接口的想法,而不是实现。通过手动接线,可以很容易地恢复原状。

答案 4 :(得分:5)

我通常不关心基于XML或反射的DI,因为在我的使用案例中,它增加了不必要的复杂性。相反,我通常会选择某种形式的手动DI,对我来说,这似乎更自然,并且具有大部分好处。

public class SawDI{
    public Saw CreateSaw(){
        return new HandSaw();
    }

    public SawMill CreateSawMill(){
        SawMill mill = new SawMill();
        mill.SetSaw(CreateSaw());
        return mill;
    }
}

// later on

SawDI di = new SawDI();
SawMill mill = di.CreateSawMill();

这意味着我仍然集中了耦合并具有其所有优点,而不依赖于更复杂的DI框架或XML配置文件。

答案 5 :(得分:3)

大多数(如果不是全部)DI容器/库为您带来的一件事是拦截通过DI创建的所有实例的方法。

答案 6 :(得分:3)

不要忘记依赖注入的一个主要缺点:您无法使用功能强大的Java IDE的Find Usages轻松找出初始化内容的位置。如果你重构很多并希望避免测试代码比应用程序代码大10倍,这可能是一个非常严重的问题。

答案 7 :(得分:2)

如果对插入的类进行硬编码,则需要在编译时使用该类。使用配置文件,您可以在运行时更改已使用的锯(在您的情况下)而无需重新编译,甚至可以使用从刚刚放置在类路径中的新jar中获取的锯。如果值得,额外的复杂性取决于你必须解决的任务。

答案 8 :(得分:2)

最近一直非常强调DI 框架,即使DI 模式被遗忘。 DI的原则为summarized by J. B. Rainsberger

  

这很简单:通过要求协作者来明确依赖关系   构造函数中的参数。重复,直到你推动所有决定   关于将哪些对象创建到入口点。当然,这只是   适用于服务(在DDD意义上)。完成。

正如您所注意到的,手动配置依赖项与使用框架之间没有太大区别。使用构造函数注入,如下所示,您的代码示例将具有更少的样板,并且编译器将强制您提供所有必需的依赖项(并且您的IDE可能会为您键入它们)。

SawMill sawMill = new SawMill(new HandSaw());
sawMill.run();

DI框架可以减少创建工厂和将对象连接在一起的样板,但同时也可以更难找出每个依赖关系的来源 - DI框架的配置是另一层挖掘抽象,你的IDE可能无法告诉你从哪里调用特定的构造函数。

DI框架的间接缺点是它们可以使依赖过于简单。如果你不能再拥有许多依赖关系feel pain,你可能会继续添加更多的依赖关系而不是重新思考应用程序的设计以减少类之间的耦合。手动连接依赖项 - 特别是在测试代码中 - 可以更容易地注意到,当您拥有太多依赖项时,测试设置变得更长,并且编写单元测试变得更加困难。

DI框架的一些优点来自于它们对AOP等高级功能的支持(例如Spring的@Transactional),范围(虽然很多次scoping with plain code就足够了)和可插拔性(如果真的需要插件框架) )。

最近我做了一个手动DI与基于框架的DI的实验。进度和结果显示为Let's Code Dimdwarf episodes 42 through 47中的截屏视频。有问题的项目有一个基于Guice的插件系统,用于创建actors,然后我使用手动DI重写,没有Guice。结果是更简单,更清晰的实现,只有更多的样板。

概要:首先尝试使用手动DI(最好是构造函数注入)。如果有很多样板,请尝试重新考虑设计以减少依赖性。如果它的某些功能为您提供了价值,请使用DI框架。

答案 9 :(得分:1)

重要的是要理解Spring基本上是两件事,一件建立在另一件之上:

  1. 轻量级DI / IoC框架和实现它的类(例如XML应用程序上下文等);和
  2. 轻量级容器
  3. (2)是Spring代码的主要部分。基本上选择一种Java技术,你可能会发现Spring有它的辅助类。这样你可以使用ActiveMQ,Sun One MQ或其他任何东西,并将它们抽象为Spring JmsTemplate,同样适用于数据访问技术,Web服务等。

    所有这些助手都使用(1)将它们连接在一起。

答案 10 :(得分:1)

使用依赖注入的最大好处之一是,在为该类创建单元测试时,可以更轻松地提供类依赖项的模拟或存根。这使您可以独立测试类,而不依赖于其协作者。

在您的示例中,无法在实例化实例化的类中模拟或存根Saw或SawMill。如果Saw和SawMill是通过setter或构造函数设置的,那么你可以在运行单元测试时传入自己的模拟Saw并模拟SawMill。

答案 11 :(得分:0)

Spring帮助您理解甚至鼓励DI模型。但是,我不相信你必须拥有Spring。

您可以使用Java配置文件,您可以调试,重构和执行代码分析。

我建议你将这些java配置文件放在另一个包中,但它们等同于XML配置文件。如果这很重要,您甚至可以动态加载这些文件。