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