本文经过范例简单地介绍Google Guice的使用,经过下面的范例咱们能够知道,Google Guice的使用很是简单。 java
Google Guice须要使用JDK1.5以上java环境。 程序员
下载Google Guice以后,有如下几个文件: spring
本例只使用到guice-1.0.jar文件,将其加入到class path中。 数据库
下面简单地介绍范例: 编程
范例1:使用com.google.inject.Module接口实现类
缓存
文件名
|
说明
|
HelloGuice.java
|
业务逻辑接口定义文件
|
HelloGuiceImpl.java
|
业务逻辑接口实现文件
|
HelloGuiceModule.java
|
该文件必须实现
com.google.inject.Module
接口
|
TestGuice.java
|
测试文件
|
HelloGuice.java 服务器
上面接口中,咱们定义了一个方法,sayHello,用于向用户问候。这里我只是作演示用,在实际的业务中,业务逻辑极可能不是这么简单。 app
HelloGuiceImpl.java
框架
该类是HelloGuice接口的实现类,这里咱们仅仅是向控制台输出了一个Hello Guice!字符串。 dom
HelloGuiceModule.java
上面的代码用于告知Guice将接口和实现类绑定。
TestGuice.java
上面的代码咱们使用JUnit来进行单元测试,这里的代码也相对比较简单。
在编写完上述代码后,咱们运行TestGuice类,将会发现它向控制台输出了Hello Guice!。
范例2:使用Java Annotation
范例1中,咱们本身手工的去配置了绑定关系,固然咱们也能够不用那么作。咱们能够直接为HelloGuice加上@ImplementedBy注释,而省略掉对com.google.inject.Module的实现。
HelloGuice.java
这里咱们使用了Guice提供的注解,ImplementedBy,表示该接口由HelloGuiceImpl类实现。这样咱们就能够不手动的去配置依赖关系。再看看TestGuice.java。
TestGuice.java
能够看出,咱们不须要本身去new一个Module了,Guice会根据咱们提供的注解本身来配置依赖关系。
咱们运行例子的时候能够看出,它也输出了Hello Guice!到控制台。
本教程是转载教程,目的是让你们了解又一个很强大的依赖注入框架Guice.
Guice 是一个依赖项注入(DI)框架。几年来我一直建议开发人员使用 DI,由于它提升了可维护性、可测试性和灵活性。经过观察工程师对 Guice 的反馈,我发现说服程序员去采用一种新技术的最好方法是使这种技术简单易用。Guice 让 DI 变得很简单,所以 Google 采用了这种方法。我但愿本文能帮助您轻松学习 Guice。
在写这篇文章时,Guice 开发团队正在奋力编写 Guice 2.0,但愿能在 2008 年末以前发布。早期的 beta 发布在 Google 代码下载站点。这是一个好消息,由于 Guice 团队添加了一些新功能,使 Guice 代码的使用和理解变得更简单。beta 版没有最终版中的一些功能,可是 beta 很稳定,质量也很好。事实上,Google 在产品软件中使用的是 beta 版。我建议您使用 beta 版。这篇文章是专门为 Guice 2.0 编写的,介绍了 Guice 的一些新功能,但没有讨论 1.0 中已经废弃的一些功能。Guice 团队向我保证:这里讨论的功能在最终发行版和当前 beta 版中都是同样的。
若是您已经了解了 DI,并且知道为何要借助一个框架来使用 DI,那么您能够跳到 经过 Guice 进行基本注入 小节。不然,请继续阅读,了解 DI 的好处。
我将以一个例子开始。假设我正在编写一个超级英雄(superhero)应用程序,同时实现一个名为 Frog Man 的 hero(英雄)。清单 1 是相关代码和第一个测试(您必定明白编写单元测试的重要性,这里就很少说了)。
清单 1. 一个基本 hero 及其测试
彷佛一切正常,但在运行测试时出现了如清单 2 所示的异常:
清单 2. 依赖项出现问题
彷佛 FrogMobile 构建了一个 HeavyWaterRefinery,假设我不能在测试中构建其中一个依赖项。固然,我能够在生产环境中实现这一点,可是不能保证能在测试中构建第二个提炼厂(refinery)。在现实生活中,您不可能提炼出氧化氘,但您能够依赖远程服务器和强大的数据库。原理是同样的:这些依赖项很难启动,交互起来也很慢,这使得测试比平时更容易失败。
为了不这个问题,您能够建立一个接口(例如 Vehicle),使 FrogMan 类接受 Vehicle 做为一个构造函数参数,如清单 3 所示:
清单 3. 依赖接口并注入它们
这种用法就是 DI 的本质 — 使类经过引用接口而不是构建接口(或使用静态引用)来接受它们的依赖项。清单 4 显示了 DI 如何使测试变得更简单:
清单 4. 测试可使用 mock 而不是麻烦的依赖项
这个测试使用了一个手动编写的 mock 对象来替换 FrogMobile。DI 不只在测试中省去了麻烦的 refinery 启动过程,并且使测试不用了解 FrogMobile 具体细节。须要的仅是一个 Vehicle 接口。除了使测试变得更简单以外,DI 还有助于提升代码的整体模块性和可维护性。如今,若是想将 FrogMobile 切换为 FrogBarge,能够不修改 FrogMan。全部 FrogMan 都依赖于 Vehicle 接口。
不过这里有一个陷阱。若是您是第一次阅读 DI,可能会想:“这下好了,如今全部 FrogMan 的调用方 都必须知道 FrogMobile(refinery、refinery 的依赖项,依此类推……)”。但若是是这样,DI 就永远不会这么流行。您能够不增长调用方的负担,而是编写一些工厂 来管理对象及其依赖项的建立。
工厂是存放框架的地方。工厂须要大量冗长重复的代码。工厂每每会让程序员(和读者)很痛苦,他们甚至会嫌它麻烦而放弃编写。Guice 和其余 DI 框架可做为 “超级工厂”,您能够经过配置它们来构建对象。配置 DI 框架比本身编写工厂容易得多。所以,程序员编写的代码大部分是 DI 样式的。测试越多代码就越好,程序员之后也就越省事。
我但愿在个人介绍以后,您会相信 DI 能为您的设计增长价值,并且使用框架会使工做更轻松。如今让咱们从 @Inject 注释和模块开始深刻讨论 Guice。
FrogMan 与 Guice 上的 FrogMan 之间的惟一区别是 @Inject。清单 5 显示了 FrogMan 带有注释的构造函数:
清单 5. FrogMan 已经加上 @Inject
一些工程师不喜欢给类添加 @Inject。他们更喜欢一个彻底忽略 DI 框架的类。这种说法有必定道理,可是我不大赞同。依赖项的注入会使注释的做用更加明显。@Inject 标记只在您要求 Guice 构建类时才有意义。若是不要求 Guice 构建 FrogMan,这个注释对代码行为就没有任何影响。这个注释恰当地指出了 Guice 将参与构建类。可是,使用它须要源代码级别的访问。若是这个注释带来不便,或者正在使用 Guice 建立没法控制其源代码的对象,那么 Guice 就会用一个替代机制。
Guice 知道您的 hero 须要一个 Vehicle 后,它须要知道提供什么 Vehicle。清单 6 包含一个 Module:一个特殊的类,用于告诉 Guice 各个接口对应的实现。
清单 6. HeroModule 将 Vehicle 绑定到 FrogMobile
模块就是一个具备某种单实例对象方法的接口。Guice 传递给模块的 Binder 用于告诉 Guice 您想如何构造对象。绑定程序 API 造成一种 区域特定语言。这种小语言容许您编写表达式代码,好比 bind(X).to(Y).in(Z)。后面将提供更多有关绑定程序做用的例子。每次调用 bind 都会建立一个绑定,Guice 将使用绑定集解析注入请求。
而后,使用 Injector 类启动 Guice。一般须要尽早在程序中建立注入器。这样 Guice 可以帮助您建立大部分对象。清单 7 包含一个以 Injector 开始的示例 main 程序:
清单 7 使用 Injector 启动应用程序
为了获取注入器,须要在 Guice 类上调用 createInjector。向 createInjector 传递一个模块列表,用于配置它自己(本例只有一个,但您能够添加一个配置 evildoer 的 VillainModule)。拥有注入器后,使用 getInstance 向它请求对象,传递您想返回的 .class(细心的读者会注意到您不须要告诉 Guice 有关 FrogMan 的信息。若是您请求一个具体类,而它有一个 @Inject 构造函数或公共非参数构造函数的话,Guice 就会建立这个类,而无需调用 bind)。
这是 Guice 构造对象的第一种方式:显式询问。可是,您不会但愿在启动例程以外使用这个操做。更好、更简单的方式是让 Guice 注入依赖项、依赖项的依赖项,依此类推(正如谚语所说:“背起地球的海龟站在另外一个海龟的背上”)。最初看来,这彷佛比较麻烦,但您很快就会习惯这种用法。例如,清单 8 显示了一个注入了 FuelSource 的 FrogMobile:
清单 8. FrogMobile 接受一个 FuelSource
这意味着,当您检索 FrogMan 时,Guice 会构建一个 FuelSource、一个 FrogMobile,最后是一个 FrogMan。即便应用程序与注入器只交互一次,也是如此。
固然,您并不老是有机会控制应用程序的 main 例程。例如,许多 Web 框架自动构建 “操做”、“模板” 或其余一些初始服务。老是能够找到一个地方插入 Guice,无论是使用该框架的一个插件,仍是使用一些本身手动编写的代码(例如,Guice 项目发布了一个 Struts 2 插件,它容许 Guice 配置您的 Strut 操做)。
到目前为止,我展现了 @Inject 应用于构造函数的用法。当 Guice 找到注释时,它会挑选构造函数参数,并试图为每一个参数找到一个配置绑定。这称为 构造函数注入。根据 Guice 的最佳实践指南,构造函数注入是询问依赖项的首选方式。但这不是惟一的方式。清单 9 显示了配置 FrogMan 类的另外一种方式:
清单 9. 方法注入
注意,我没有使用注入的构造函数,而是改用一个带有 @Inject 标记的方法。Guice 会在构造好 hero 以后当即调用此方法。Spring 框架的忠实用户能够将此方法视为 “setter 注入”。不过,Guice 只关心 @Inject;您能够任意命名这个方法,它能够带有多个参数。此方法能够是包保护的,也能够是私有方法。
若是您认为 Guice 访问私有方法不是很好,能够参见清单 10,其中 FrogMan 使用了字段注入:
清单 10. 字段注入
一样,全部 Guice 都只关心 @Inject 注释。字段注入查找注释的全部字段并试图注入相应的依赖项。
三个 FrogMan 版本都展现了相同的行为:Guice 在构建时注入相应的 Vehicle。不过,像 Guice 的做者同样,我更喜欢构造函数注入。下面简单分析这三种方式:
如今,假设应用程序中有多个 Vehicle。同样英勇的 Weasel Girl 没法驾驭 FrogMobile!同时,您不想在 WeaselCopter 上硬编码依赖项。清单 11 显示了 Weasel Girl 请求一种更快的传输模式:
清单 11. 使用注释请求某种特定的实现
在清单 12 中,HeroModule 使用绑定函数告诉 Guice WeaselCopter 是 “很快” 的:
清单 12. 告诉 Guice Module 中的相关注释
注意,我选择了一个注释,描述我想以抽象形式描述的工具种类(@Fast),而不是与实现太接近的注释(@WeaselCopter)。若是您使用的注释将想要的实现描述得太精确,就让读者以为建立一个隐式依赖项。若是使用 @WeaselCopter,并且 Weasel Girl 借用了 Wombat Rocket,就会对程序员阅读和调试代码形成混淆。
要建立 @Fast 注释,须要复制清单 13 中的模板:
清单 13. 复制粘贴这段代码以建立一个绑定注释
若是您编写了大量 BindingAnnotations,就会获得许多这样的小文件,每一个文件只是注释名称不一样。若是您以为这很繁琐,或者须要执行快速的原型设计,能够考虑 Guice 的内置 @Named 注释,它接受一个字符串属性。清单 14 展现了这种替代方法:
清单 14. 使用 @Named 代替自定义注释
这种方法是可行的,但因为名称只在字符串内有效,因此这不能利用编译时检查和自动补齐。总的来讲,我更愿意本身编写注释。
若是您根本不想使用注释,怎么办?即便添加 @Fast 或 @Named("Fast") 都会使类在某种程度上影响配置自己。若是想知道如何解决这个问题,请接着阅读。
若是每次探险都派遣 Frog Man,您可能会厌烦。您喜欢在每一个场景中出现的 hero 是随机的。可是,Guice 的默认绑定程序 API 不容许出现 “每次调用时将 Hero 类绑定到一个不一样的实现” 这样的调用。不过,您能够 告诉 Guice 使用一种特殊的方法来建立每一个新的 Hero。清单 15 显示了将一个新方法添加到 HeroModule 中,并用特殊的 @Provides 注释进行注释:
清单 15. 使用 provider 编写自定义建立逻辑
Guice 会自动发现具备 @Provides 注释的 Module 中的全部方法。根据 Hero 的返回类型,在您请求某个 hero 时,Guice 会进行计算,它应该调用 provider 方法来提供 hero。您能够为 provider 方法添加逻辑以构建对象并在缓存中查询它,或者经过其余方式得到它。provider 方法是将其余库集成到 Guice 模块中的很好方式。它们也是从 Guice 2.0 开始提供的新方法(Guice 1.0 中只编写自定义 provider 类,这比较乏味,并且更加繁琐。若是您已经决定使用 Guice 1.0,用户指南中有这种旧方法的文档,并且在本文随附的 示例代码 中,您能够找到一个自定义 provider)。
在清单 15 中,Guice 自动使用正确的参数注入 provider 方法。这意味着 Guice 将从它的绑定列表中找到 WeaselGirl 和 FrogMan,您无需在 provider 方法中手动构建它们。这演示了 “海龟背地球” 原则(海龟背地球,哪海龟下面是什么呢?是由另外一只海龟背着,如此反复)。您依靠 Guice 来提供依赖项,即便是配置 Guice 模块自己。
假设一个故事(Saga)中要有多个 hero。若是要求 Guice 注入一个 Hero,只会获得一个 hero。但若是您请求一个 “hero provider”,就能够根据须要建立任意多的 hero,如清单 17 所示:
清单 17. 注入 provider 来控制实例化
提供者也能够推迟英雄的出场时间,直到传奇真正开始。若是英雄依赖于时间敏感或上下文敏感的数据,这就会很方便。
Provider 接口有一个方法:get。要访问提供的对象,调用这个方法便可。每次有没有获取新对象以及对象如何配置取决于 Guice 是如何配置的(参阅下面的 做用域 部分,了解单实例对象和其余长生命周期对象的详细信息)。在本例中,Guice 使用 @Provides 方法,由于它是构建新 Hero 的注册方式。这意味着该传奇应该由任意三位英雄混合而成。
不要把提供者与 provider 方法弄混淆了(在 Guice 1.0,这二者更难区分开来)。尽管该 Saga 是从自定义 @Provides 方法中得到它的英雄,但您能够请求任意 Guice 实例化依赖项的一个 Provider。若是须要,能够根据清单 18 从新编写 FrogMan 的构造函数:
清单 18. 请求 Provider 而不是依赖项
(注意您彻底不用更改这个模块代码)。从新编写没有任何做用;只是说明您老是能够请求 Provider,而不用直接请求依赖项。
默认状况下,Guice 为每一个请求的依赖项建立一个新实例。若是对象是轻量级的,这个策略能够很好地工做。可是,若是有一个建立开销很大的依赖项,就可能须要在几台客户机之间共享实例。在清单 19 中,HeroModule 将 HeavyWaterRefinery 做为一个单实例对象绑定:
清单 19. 将 HeavyWaterRefinery 绑定为一个单实例对象
这意味着 Guice 会一直保持 “提炼厂” 可用,只要另外一个实例须要燃料源,Guice 就会注入相同 的 “提炼厂”。这避免了在应用程序中启动多个 “提炼厂”。
在选择做用域时,Guice 提供了一个选项。可使用绑定程序配置它们,或者直接注释依赖项,如清单 20 所示:
清单 20. 改用注释选择做用域
Guice 提供了超出范围的 Singleton 做用域,但它容许您定义本身的做用域(若是您愿意)。例如,Guice servlet 包提供了两个其余做用域:Request 和 Session,它们为 servlet 请求和 servlet 会话提供类的一个独特实例。
HeavyWaterRefinery 须要一个许可密钥才能启动。Guice 能够绑定常量值和新实例。请查看清单 21:
清单 21. 在模块中绑定常量值
这里有必要使用绑定注释,不然 Guice 将不能区分不一样的 String。
注意,尽管前面不推荐使用,我仍是选择使用 @Named 注释。由于我想显示清单 22 所示的代码:
清单 22. 使用属性文件配置模块
这段代码使用 Guice Names.bindProperties 实用函数,经过恰当的 @Named 注释将 app.properties 文件中的每一个属性与一个常量绑定。这自己就很好,并且还显示了您可使模块代码更复杂。若是喜欢,能够从数据库或 XML 文件加载绑定信息。模块是纯 Java 代码,这提供了很大的 灵活性。
Guice 主要概念小结:
须要了解的 Guice 知识还不少,但您应该先掌握这篇文章中讨论的内容。我建议下载它,以及本文的 示例代码。固然,您也能够建立本身的示例应用程序,这就更好了。经过示例深刻了解概念但又不用考虑生产代码是颇有意思的。若是要了解更多 Guice 高级功能(好比面向方面编程支持),建议您访问 参考资料 中的一些连接。
说到生产代码,DI 的一个缺点是它可能感染病毒。注入一个类后,它会致使注入下一个类,依此类推。不过这很好,由于 DI 使代码更好。另外一方面,这须要大量重构现有代码。为了使工做易于管理,能够将 Guice Injector 存储在某处并直接调用它。应该将这看成一根临时须要的拐杖,但最后必定能够摆脱它。
Guice 2.0 即将推出。有一些功能我尚未讨论,它可使模块的配置更简单,并且能支持更大、更复杂的配置方案。
我但愿您会考虑将 Guice 添加到工具包中。根据个人经验,DI 对于实现灵活的可测试代码库特别有用。Guice 使 DI 变得简单而有趣。还有什么比容易编写的、灵活的、可测试的代码更好呢?