InfoQ Java 的定期撰稿人 Alex Blewitt 博士最近出版了《精通Eclipse 插件开发》,该书是去年出版的《 Eclipse 4 插件开发实例》的续写。与其前任类似,这本书本质上来说也是一本教程,不过它假设读者对构建 Eclipse 集成开发环境插件的基础已经比较熟悉,可以快速深入更高级的主题。
该书的前两章主要探究开发者如何针对 Eclipse 框架的各个部分进行插件开发,以构建一个可以读取 RSS 和 Atom 源的新闻阅读器示例。第一章使用 JFace——用于构建 Eclipse 用户界面的基于标准窗口工具包(SWT)的组件库——来创建基础的新闻摘要向导。这个向导会被整合到通用导航框架(Common Navigator Framework)中,然后包浏览器调用该框架,为用户提供项目内容的树状视图。
第二章介绍了 Eclipse 扩展的注册机制。这一机制已经广泛应用于 OSGi 运行时环境中,不过本书作者向读者展示了如何在该环境外使用这一机制构建一个可以让其他插件在 OSGi 或 Eclipse 运行时环境之外提供功能的插件。
本书剩余部分的注意力则主要集中于 OSGi 框架之上。第三章为读者提供了关于 OSGi 服务的精彩介绍,这是在其他 Eclipse 插件开发书籍中很少提及的。OSGi 服务可以有多个不同版本在运行时环境同时共存并且还可以在其他的 OSGi 环境(如 Felix)中正常运行。本章中还包含了声明式服务(Declarative Services)和 Blueprint 的详细对比,声明式服务是在 OSGi 运行时环境中以声明的形式实例化服务的原有机制,而 Blueprint 则是在几个小版本之后引入的另外一种机制,本质上是为了完成了同样的工作。
第四章介绍了内嵌于 Eclipse 4 并且 Felix 和 Equinox 均已采用的 Gogo shell。在探究了包括变量、字面值和函数在内的语法之后,作者继续介绍如何通过使用 Gogo 自带的 osgi:addCommand 命令和编写自定义的 Java 类并将其注册为 OSGi 服务,扩展控制台的能力。第五章介绍了如何将原生代码加载到 OSGi 或 Eclipse 应用中,除此之外还介绍了如何利用片段包(fragment bundles)扩展框架的能力。
第六章详细介绍了类加载器(ClassLoader),以及 OSGi 如何利用类加载器支持包的分离。由于许多类库错误地假设每个 JVM 只有一个类加载器,Blewitt 还调查了这些类库的升级策略,以便它们可以在 OSGi 环境中正常运行。
构建 Eclipse 插件或任何基于 OSGi 的应用都需要将组件分割成更小的高内聚低耦合的模块,这个过程有时可能比较困难。在第七章中,作者为读者展示了一些通用技术、设计模式和当前最佳实践以帮助这部分工作的完成。
最后几章覆盖了如下主题:使用 OSGi 事件总线构建反应式应用,用 Eclipse P2 生成升级站点以及如何为 Eclipse 编制帮助文件。
本书的编写思路简明清晰,并且辅以精心选择的实例,能够帮助读者真正理解讨论中涉及的各种概念。本书的大部分内容针对模块化做了详细阐述,特别是 Eclipse 自身所使用的 OSGi 框架。此外,本书还提供了关于类加载的精彩介绍。
InfoQ 采访了本书作者 Alex,尝试挖掘出更多关于本书的信息。
InfoQ:你是否早就打算为您的第一本书编写续集,如果没有,那是什么促使您这样做呢?
Alex:在编写完《Eclipse 4 插件开发实例》之后,我原计划休息一段时间不再编写新的书籍;写书是件十分耗费精力的事情,特别是当你还有全职工作要做的时候。不过因为第一本书销量相当不错,出版商询问我是否有兴趣编写一本更加深入一些的书籍。而我确实有一些想法想要写出来,特别是关于 OSGi 模式这方面,而且这次续写的机会太好了,也让人难以割舍。
而且由于篇幅和所面向的受众群体等原因,在第一本书里有些主题并没有覆盖到;譬如说,简单的插件并不需要创建向导、更新站点和帮助文档。不过,如果要构建一个稍大一些的 Eclipse 插件集合或者希望其他人也能够在这些框架之上继续构建,那就需要理解扩展注册机制是如何工作的以及如何创建定制化的扩展点。在进阶书籍中,在类加载机制和 OSGi 服务层等方面还可以走得更加深入一些。
InfoQ**:这本书为读者提供了关于 OSGi和类加载器的相当精彩的介绍。除了 Eclipse插件开发者之外,你认为还有哪些开发人员会从中受益?**
Alex:OSGi 是过去十年中最未被充分重视的技术之一。对于大型的 Java 服务来说,OSGi 无处不在,更不用说各种 IDE。Adobe 有许多工具是构建在 OSGi 之上的,诸如 Liferay 和 Paremus 之类的大规模系统也使用 OSGi 驱动它们的平台。对于上面这些案例来说,其主要的关注点都在于模块化和低耦合的服务。现在我们看到其他的平台上逐渐出现这些特征;向不同的 REST 服务迁移的一般趋势就是这样一个例子。通过模块化管理代码复杂性的理念是最终的必然选择——就在我们谈话的时候,即使是 JDK 都正在被模块化。不过与模块化密不可分则是向后兼容,或者版本兼容。大部分开发人员默认会遵循语义化的版本控制(Semantic Versioning)(更多详情参见 http://www.semver.org )而其中的核心理念是不要破坏向后兼容的客户端。不论你正在构建 Java 服务或者用 REST 服务联结多种不同的语言,这一点仍然适用;如果你不能控制整个单元的部署,那就必须要考虑兼容性问题。像 Scala 这类语言,认为向后兼容性的代价太高而没有必要这么做,这也就是为什么通常在这些语言中见不到像 Java 那么多样的类库生态系统。
回到之前关于 OSGi 章节的问题——我觉得 Eclipse 正在朝更多 OSGi 服务的方向发展而不是静态工厂访问方法。领会 Eclipse 未来发展方向而非过去发展历史的关键在于理解 OSGi 的服务模型,因此应该是 Eclipse 插件开发作者的必读书目。然而,更大范围的 Java 世界也正在朝模块化的方向转移,因此向后兼容就变得愈发重要。从 OSGi 模型中我们可以学到很多经验,即使是对于那些以前没有了解过 OSGi 的 Java 开发者来说也会受益良多。我希望我所写的这本书可以让读者了解到如何用 OSGi 完成工作,而不是自以为是地向读者介绍为什么其他人数十年来一直使用它。
关于类加载器,虽然 Java 开发人员无形中一直在使用它,但很少有人能完全理解它。一个类加载器提供两类服务;一个服务是从(加载自某一来源;可能是本地或远程的 URL,也可能是一个数据库,甚至还可能是动态生成的)字节序列中创建一个类,另一个服务则是将这个类的空间划分到不同的区段。例如,如果你部署了两个使用 Log4J 的 Web 应用到一个 Tomcat 或 Jetty 服务器上,每个 Web 应用都会分别加载 Log4J 的类。这样,在运行时就会有两个 LogFactory 类被加载到 JVM 中;其中一个对第一个 Web 应用可见,而另外一个则只对第二个 Web 应用可见。即使是一个静态的引用——例如 LoggerFactory.getLogger(Example.class) 返回的引用——也会有两个 Logger 类的实例,每个实例只对一个类加载器可见。尽管 Java 语言让它看起来像一个单例,实际上只有幼稚的 Java 开发人员才会认为一个 JVM 只有一个实例。这样,不同的 Web 应用就可以有不同的日志级别,这对于在一个共享实例的服务器上调试单个应用的场景来说非常实用。撰写类加载器介绍的一部分原因是为了解释它们的工作机制,因为类加载器是所有的 Java 应用和应用服务器运转的基础,虽然这一点并不那么显而易见。
InfoQ**:对我而言,从来也没有弄清楚为什么 OSGi既有声明式服务(从版本 4开始引入)又有 Blueprint**(从版本 4.2开始引入)——看起来这两种服务所起到的作用基本上是一致的。为什么最终会同时存在两个服务呢?
**Alex****:** 声明式服务和 Blueprint 两者都提供了一种执行服务和服务依赖自动组装的方法,从这个角度来说他们是相似的。在底层实现上,这两种服务都使用 OSGi 服务模型;他们寻找不断变化的服务并基于此动态地重新配置系统。
声明式服务更加接近于服务运行时的本质;声明式服务一直处于等待状态,直到发现对某个服务的需求,并且在发布这个服务之前,它所有的依赖都是可用的。所以,假设你正在查找一个 CacheService,只有这个 CacheService 的所有依赖都满足要求,这个服务才会存在。这可以解决应用启动时可能会出现的诸多排序问题;在它的支持层存在之前,即使有了 CacheService 服务也没有任何意义,所以你根本就不需要看到它。
而 Blueprint 则会在服务实现可用之前发布服务代理。他们大胆假设在将来的某个时间点这个服务必然会存在,所以将 CacheService 提前发布出来。当客户端第一次尝试使用这个服务时,它会尝试实例化并满足全部的依赖关系,如果依赖关系无法满足,就会抛出一个运行时异常。
Blueprint 与声明式服务的另一个区别在于获取到的代理可以被分发给各种不同的对象并且可以被持久化成某个类的最终成员变量;一旦分配了一个代理,你将一直使用这个代理,即使底层的服务不断地发生变化。这样对于不熟悉 OSGi 动态特性的开发者,可以更加容易地将那些不期望服务不断变化的 Java 代码与服务集成。Blueprint 的配置严重也依赖于 Spring 的 Bean 配置文件,这样从 Spring 迁移到更加动态的 OSGi 模型时,会更加容易理解。
正如我在书中所提到的,如果迁移之前的环境是 Spring,那么用 Blueprint 上手会更加容易一些,只需要简单地复制粘贴 Spring 的配置文件就可以完成迁移工作。如果你之前没有 Spring 的经验,那么声明式服务可能会更简单一些。不过这两种情况下,你只需要基于一个服务的依赖表达这个服务即可,系统将会帮你完成实例化和组装的工作,而不需要手动完成所有的工作。最终,这两种服务在底层都使用了 OSGi 的服务模型,像 REST 一样,如何获取服务并不重要,当服务准备就绪后,只需要用标准 API 与其交互即可。
InfoQ**:在关于 Blueprint的讨论中,你好像对 Gemini持批评态度,考虑到其对 Spring的依赖,同时注意到在 Spring应用开发者向 OSGi迁移时这一点可能会很有用。我在想关于这一点你是否能稍微展开说一下。OSGi是否提供与 Spring类似的能力?你是否认为 OSGi环境中遗留的 Spring**内核是必要的?
Alex:Gemini 和 Aries 都提供 Blueprint 服务,就像 Jetty 和 Tomcat 都支持 Servlet 一样。就像你即可以将一个 WAR 包部署到 Jetty 也可以部署到 Tomcat 一样,如果一个 OSGi 应用使用了 Blueprint 服务,你即可以将其部署到包含 Gemini 的 OSGi 容器中也可以将其部署在包含 Aries 的 OSGi 容器中。考虑等价的包(bundles)时,(在充分考虑其他情况前提下)这两种情况的差异之一就是它们所需要的依赖关系集。Aries 需要 5 个包(其中 2 个是 SLF4J 日志包)。Gemini 则需要安装 9 个包,其中 6 个是 Spring 包。此外,Spring 不再与 OSGi 的元数据一起构建,如果想要使用它,就必须要从资源库中后置处理这些包。
关于 Spring 是不是必须遗留在 OSGi 环境中……,有许多基于 Spring 的好项目完成了很好的工作,其中的大部分都以某种方式与 Spring 上下文进行交互。Spring 的确普及了依赖注入技术,并且将目标从‘如何实例化 Bean’变成了‘请给我这个 Bean’。通过服务 OSGi 也可以完成相同的工作;关键的区别在于服务可以被动态的移除、重启、升级甚至是在线替换。当然,并不是每个应用都需要这种层次的动态性而且 Spring 应用通常会以整体的方式进行构建和部署,因此要根据不同的目标采用不同的方案。尽管 OSGi 很好地实现了动态特性,可能有些读者已经发现分区化和模块化应用的能力会带来更大的收益。总结一下,Spring 能够将应用开发提升到一定层次——松耦合的 Bean——不过在应用部署和配置方面仍然需要将其作为一个整体看待。OSGi 则接手了 Spring 遗留的问题,能够动态地更新应用配置并且可以在线替换应用,而不需要停机时间来重新启动应用。这在分布式服务能够发挥作用的云端环境或网络环境下特别有用。
我想最终一旦你迁移到一个 OSGi 平台,Spring 容器可能会不再适用,不过在这个过程中 Spring 还是有很多用处。Blueprint 可以帮助完成从 Spring 到 OSGi 的转化。在平台即服务和云端部署方面很可能会看到 OSGi 的大幅增长;可以将单个 OSGi 应用在重复的分布式服务集合启动。
关于本书作者
Alex Blewitt**** 博士在伦敦的一家投资银行工作,业余时间跟踪 OSGi 和 Eclipse 相关的最新资讯。尽管之前曾经作为 EclipseZone 的编辑和 2007 年 Eclipse 大使的提名者,他的日常工作与 Java 和 Eclipse 都没关系。他剩下的很有限的时间主要用来陪伴他年轻的家庭成员,如果天气好,会带他们去飞行。
查看英文原文: Book Review and Interview: Mastering Eclipse Plug-in Development
评论