依赖注入(Dependency Injection)
Spring的两个核心内容为控制反转(Ioc)和面向切面(AOP),依赖注入(DI)是控制反转(Ioc)的一种方式。
依赖注入这个词让人望而生畏,现在已经演变成一项复杂的编程技巧 或设计模式理念。但事实证明,依赖注入并不像它听上去那么复杂。 在项目中应用DI,你会发现你的代码会变得异常简单并且更容易理解 和测试。
DI功能是如何实现的
任何一个有实际意义的应用(肯定比Hello World示例更复杂)都会由 两个或者更多的类组成,这些类相互之间进行协作来完成特定的业务 逻辑。按照传统的做法,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代 码。
举个例子,考虑下程序清单1.2所展现的Knight类。
程序清单1.2
DamselRescuingKnight只能执行RescueDamselQuest 探险任务
可以看到,DamselRescuingKnight在它的构造函数中自行创建了 Rescue DamselQuest。这使得DamselRescuingKnight紧密地 和RescueDamselQuest耦合到了一起,因此极大地限制了这个骑 士执行探险的能力。如果一个少女需要救援,这个骑士能够召之即 来。但是如果一条恶龙需要杀掉,或者一个圆桌……额……需要滚起 来,那么这个骑士就爱莫能助了。
更糟糕的是,为这个DamselRescuingKnight编写单元测试将出奇 地困难。在这样的一个测试中,你必须保证当骑士的 embarkOnQuest()方法被调用的时候,探险的embark()方法也要 被调用。但是没有一个简单明了的方式能够实现这一点。很遗 憾,DamselRescuingKnight将无法进行测试。
耦合具有两面性(two-headed beast)。一方面,紧密耦合的代码难以 测试、难以复用、难以理解,并且典型地表现出“打地鼠”式的bug特 性(修复一个bug,将会出现一个或者更多新的bug)。另一方面,一 定程度的耦合又是必须的——完全没有耦合的代码什么也做不了。为 了完成有实际意义的功能,不同的类必须以适当的方式进行交互。总 而言之,耦合是必须的,但应当被小心谨慎地管理。
通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在 创建对象的时候进行设定。对象无需自行创建或管理它们的依赖关系,如图1.1所示,依赖关系将被自动注入到需要它们的对象当中 去。
图1.1
依赖注入会将所依赖的关系自动交给目标对象,而不是让对象自己去获取 依赖 为了展示这一点,让我们看一看程序清单1.3中的BraveKnight,这 个骑士不仅勇敢,而且能挑战任何形式的探险。
程序清单1.3
BraveKnight足够灵活可以接受任何赋予他的探险任 务 我们可以看到,不同于之前的 DamselRescuingKnight,BraveKnight没有自行创建探险任 务,而是在构造的时候把探险任务作为构造器参数传入。这是依赖注 入的方式之一,即构造器注入(constructor injection)。
更重要的是,传入的探险类型是Quest,也就是所有探险任务都必须 实现的一个接口。所以,BraveKnight能够响应 RescueDamselQuest、 SlayDragonQuest、 MakeRound TableRounderQuest等任意的Quest实现。