卷1:第6章 Eclipse之一

原作者:Kim Moir 原地址:http://www.aosabook.org/en/eclipse.html

因为经验不足,有些地方可能翻译的不好,后续还会抽时间斟酌完善,大家发现问题请尽管拍砖,多谢!

几个术语的翻译:

artifact:工件

plugin:插件

committers:提交者

众所周知,实现软件的模块化是件困难的事情。管理不同社区开发的大量代码之间的互相合作也是件困难的事情。对于Eclipse来讲,在这两方面上都取得了成功。在2010年6月,Eclipse基金会发布了Helios合作版本,它由来自超过40个公司的39个项目团队和490个提交者来协作构建基础平台的功能。Eclipse起初的架构愿景是什么?它是怎样进化的?一个应用的架构是如何做到鼓励社区合作和成长?让我们从头开始。

2001年11月7日,一个名为Eclipse 1.0的开源项目释放。当时,Eclipse被描述为“一个并无任何特殊的集成开发环境(IDE)”。这个描述被有意简单化了,因为其架构愿景并不是又一个工具集而是一个框架,这个框架是模块化且可扩展的。Eclipse提供了基于组件的平台,它作为构建开发人员工具的基础。这种可扩展的架构鼓励社区基于一个核心平台来进行扩展以突破其最初的局限性。Eclipse SDK允许开发人员将其作为宿主环境(self-host)并利用Eclipse SDK本身来构建新版本的Eclipse。

开源开发者的经典形象是一个怀有奉献精神的人熬到深夜修改bug并实现有趣的新功能来满足个人兴趣。相反的,回顾一下Eclipse的早期历史,它最初的一些代码是IBM开发的VisualAge所贡献的。这个开源项目的最初贡献者是IBM的一家名为OTI的子公司(Object Technology International)。这些提交者将全部的工作时间用在开源项目上,他们在新闻群组上回答提问、修改bug、实现新功能。一些对其感兴趣的软件供应商也组织起来为扩展这个开放工具的功能付出了努力。最初的Eclipse参与成员是Borland、IBM、Merant、QNX软件系统、Rational软件、红帽、SuSE和TogetherSoft。

通过努力,这些公司基于Eclipse构建出了商用的产品。类似于一些公司在Linux内核上进行投入,他们让自己的雇员来提高开源软件,而开源软件又成为其商业产品的基础。在2004年的早期,Eclipse基金会成立来管理和扩张日益成长的Eclipse社区。这个非盈利性的基金会通过企业会员的会费来募集资金并由理事会来进行管理。时至今天,Eclipse社区已经扩展到包含170多个会员企业和近1000个贡献者。

最初,Eclipse作为SDK被人所知,但是现在它包含了更多的内容。截止到2010年7月,在eclipse.org中有250个不同的项目处于开发之中。有各种工具来支持C/C++、PHP、web services、模型驱动开发以及构建工具等。所有的这些项目都被包含在一个顶级项目(TLP)之中,这个顶级项目由高级会员所组成工程管理委员会(PMC)所管辖以负责技术方向和发布目标。简洁起见,本章只涉及到Eclipse SDK中的Eclipse项目和运行时Equinox项目的架构进化。鉴于Eclipse产品有一个很长的发展历史,我们将会关注早期的Eclipse以及3.0、3.4和4.0释放版本。

6.1 早期的Eclipse 在21世纪初期,有许多的软件开发工具但是它们中很少能协同工作。Eclipse试图提供一个开源平台,基于此平台可以为应用开发人员构建互操作的工具。这将使得开发人员(译者注——此处应该值得是工具开发人员)集中精力实现新的工具,而不会再书写诸如文件系统交互、提供软件更新以及连接源码库这样的基础设施事务。Eclipse可能作为 Java开发工具(JDT)为人所熟知。而其真正的意图可理解为这些优秀的Java开发工具能够作为样例,并提供给那些有兴趣开发其它语言工具的人们。

在深入了解Eclipse架构之前,让我们看一下对于开发人员来讲Eclipse SDK是什么样子的。在启动Eclipse并选择工作台后,展现在你面前的将会是Java透视图(perspective)。透视图根据当前使用的特定工具来组织视图(view)和编辑器(editor)。

enter image description here

图6.1 Java透视图 早期版本的Eclipse SDK架构包含三个主要的元素,其分别对应三个主要的子项目:Platform、JDT(Java Development Tools ,Java开发工具集)和PDE(Plug-in Development Environment,插件开发环境)。

6.1.1 Platform Eclipse平台基于Java来实现因此需要Java虚拟机来运行。它是由名为插件的小功能单元所组成的。插件是Eclipse组件模型的基础。插件在本质上来讲是一个包含manifest的JAR文件,manifest进行了JAR的自描述如它的依赖、怎样使用或扩展。最初,manifest信息存储在plugin.xml中,这个文件位于插件的根目录中。Java开发工具集提供了用Java开发代码的插件。PDE(Plug-in Development Environment,插件开发环境)提供了开发插件扩展Eclipse的工具。Eclipse插件使用Java来实现,但是也可以包含非代码内容如提供在线文档的HTML文件。每个插件都有自己的类加载器(class loader)。插件可以通过在plugin.xml中使用requires语句声明对其它插件的依赖。查看一下org.eclipse.ui插件的plugin.xml文件,你能看到指定了名字和版本号以及它需要从其它插件导入的依赖。

<?xml version="1.0" encoding="UTF-8"?>
<plugin
  id="org.eclipse.ui"
  name="%Plugin.name"
  version="2.1.1"
  provider-name="%Plugin.providerName"
  class="org.eclipse.ui.internal.UIPlugin">

  <runtime>
    <library name="ui.jar">
      <export name="*"/>
      <packages prefixes="org.eclipse.ui"/>
   </library>
</runtime>
<requires>
  <import plugin="org.apache.xerces"/>
  <import plugin="org.eclipse.core.resources"/>
  <import plugin="org.eclipse.update.core"/>
  :       :        :
  <import plugin="org.eclipse.text" export="true"/>
  <import plugin="org.eclipse.ui.workbench.texteditor" export="true"/>
  <import plugin="org.eclipse.ui.editors" export="true"/>
</requires>
</plugin>

为了鼓励人们基于Eclipse平台进行构造,这就需要一种对平台进行扩展的机制而平台要能够接受这种扩展。这是通过使用扩展和扩展点来实现的,它是Eclipse组件模型的另一个元素。通过使用export来指明你希望其他人在实现扩展时能够使用的接口,这将会保证在你的插件外能够使用到的类仅限于export的那部分。它同时提供了插件外所能够访问资源的额外限制,这与将所有的public方法和类公开给用户截然不同。导出的扩展被视为公开的API。而其它的被视为私有实现细节。要实现一个能够出现在Eclipse工具栏的菜单项,你可以使用org.eclipse.ui的actionSets扩展点。

<extension-point id="actionSets" name="%ExtPoint.actionSets"
             schema="schema/actionSets.exsd"/>
<extension-point id="commands" name="%ExtPoint.commands"
             schema="schema/commands.exsd"/>
<extension-point id="contexts" name="%ExtPoint.contexts"
             schema="schema/contexts.exsd"/>

<extension-point id="decorators" name="%ExtPoint.decorators" schema="schema/decorators.exsd"/> <extension-point id="dropActions" name="%ExtPoint.dropActions" schema="schema/dropActions.exsd"/>

在你的插件中,对org.eclipse.ui.actionSet扩展点的扩展实现了添加菜单项功能,如下:

<?xml version="1.0" encoding="UTF-8"?>
<plugin
  id="com.example.helloworld"
  name="com.example.helloworld"
  version="1.0.0">
   <runtime>
       <library name="helloworld.jar"/>
    </runtime>
   <requires>
      <import plugin="org.eclipse.ui"/>
    </requires>
   <extension
      point="org.eclipse.ui.actionSets">
      <actionSet
        label="Example Action Set"
        visible="true"
        id="org.eclipse.helloworld.actionSet">
     <menu
           label="Example &Menu"
           id="exampleMenu">
        <separator
              name="exampleGroup">
        </separator>
     </menu>
     <action
           label="&Example Action"
           icon="icons/example.gif"
           tooltip="Hello, Eclipse world"
           class="com.example.helloworld.actions.ExampleAction"
           menubarPath="exampleMenu/exampleGroup"
           toolbarPath="exampleGroup"
           id="org.eclipse.helloworld.actions.ExampleAction">
     </action>
  </actionSet>
 </extension>
</plugin>

当Eclipse启动的时候,运行平台会扫描所有安装插件的manifest并在内存中构建插件注册器。扩展点及其对应的扩展通过名字进行匹配。最终生成的注册器可以通过Eclipse平台提供的API进行访问。注册器信息缓存在硬盘上,Eclipse在重新启动的时候这些信息可以被重新加载。所有的插件在启动的时候被注入到注册器中,但是在实际代码使用前不会被激活(类加载)。这种方式叫做懒激活。通过在需要的时候才加载插件关联的类,能够使得添加额外组件带来的性能影响得到降低。例如,提供org.eclipse.ui.actionSet扩展点的插件在用户选择工具栏的新菜单项之前不会被激活。

图6.2 示例菜单

图6.2 示例菜单

生成菜单项的代码如下所示:

package com.example.helloworld.actions;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.jface.dialogs.MessageDialog;

public class ExampleAction implements IWorkbenchWindowActionDelegate {
    private IWorkbenchWindow window;

public ExampleAction() {
}

public void run(IAction action) {
    MessageDialog.openInformation(
        window.getShell(),
        "org.eclipse.helloworld",
        "Hello, Eclipse architecture world");
}

public void selectionChanged(IAction action, ISelection selection) {
}

public void dispose() {
}

public void init(IWorkbenchWindow window) {
    this.window = window;
}

}

一旦用户点击了选择了新的菜单项,实现这个扩展点的插件将会查询扩展注册器。提供扩展的插件会初始化功能提供者并加载插件。一旦这个插件被激活,在我们的例子中ExampleAction构造函数将会被执行,然后初始化一个工作台操作代理(Workbench action delegate)。当在工作台进行了选择且代理已被创建完成,实际的操作就会执行。信息提示框将会弹出这样的信息“Hello, Eclipse architecture world”。

这种可扩展的架构是Eclipse生态系统成功成长的关键因素之一。公司或个人可以开发新的插件,既可以作为开源释放可以商业出售。

Eclipse最重要的理念之一就是任何事情都是插件。不管这个插件是包含在Eclipse平台中,还是你自己写的,插件都是这个装配式应用的一等组件。图6.3展现了早期Eclipse版本中以插件方式实现的相关功能。

图6.3 早期的Eclipse架构

图6.3 早期的Eclipse架构 对于Eclipse平台的用户来说,工作台是最熟悉的UI组件了,因为它提供了Eclipse在桌面上怎样展现给用户的结构。工作台包括透视图、视图和编辑器。编辑器会与文件类型相关联,所以当某个文件被打开时能够用正确的编辑器打开。可以将“问题”(problem)视图作为视图的例子,它会显示你Java代码中的错误或警告。编辑器和视图组合起来形成一个透视图,从而给用户以良好的样式展现工具。

Eclipse工作台是基于SWT(Standard Widget Toolkit ,标准组件工具集)和JFace构建的,我们需要关注一下SWT。组件工具集通常可分为原生的和仿真的。原生的组件工具集使用操作系统调用来构建诸如列表和按钮这样的用户界面组件。与组件的交互通过操作系统来处理。仿真的组件工具集在操作系统以外实现组件,自己处理鼠标和键盘、绘图、焦点以及其它的组件功能,并非遵从操作系统(的机制)。以上两种设计都有其长处和不足。

原生的组件工具集是“像素完美”(pixel perfect)的。它们的组件看上去及使用上与其它桌面应用的对应组件类似。操作系统供应商会不断地修改组件的外观和体验并添加新的特性。原生的组件工具集可以自由进行升级。但是,原生的组件工具集很难实现,因为底层的操作系统组件在实现上差异很大,这导致了不一致性和繁重的代码。

仿真的组件工具集要么提供自己的外观和体验,要么试图在外观和行为上模拟操作系统。它们的巨大优势在于其灵活性(尽管现代的原生组件工具集如Windows Presentation Framework (WPF)也一样灵活)。因为实现组件的代码是工具集的一部分而不疏嵌入到操作系统中,可以以任何方式确定组件的外观和行为。使用仿真组件工具集的代码将会非常简便。早期的仿真组件工具集并没有获得好的评价。它们通常比较慢并且在模拟操作系统方面做得很差,这使得它们不适合用在桌面应用上。尤其是在当时,Smalltalk-80程序因为他们使用了模拟组件而很容易辨认出来。用户意思到他们正在运行“Smalltalk”程序,这降低了Smalltalk应用的接受度。

不像C和C++等计算机语言,Java的第一本版本就包含了一个本地组件工具集名为抽象窗口工具包(Abstract Window Toolkit ,AWT)。AWT被视为功能有限、有缺陷且前后不一致,因此广受责难。在Sun公司以及其它任何地方,鉴于AWT的(糟糕)经验,人们认为实现便利高效的原生组件工具集是不现实的。后来的解决方案是Swing,它是一个功能完备的模拟组件工具集。

大约在1999年,OTI使用Java实现了一个名为VisualAge Micro版的产品。VisualAge Micro版的第一个版本使用了Swing而OTI使用Swing的体验并不好。早期的Swing版本有缺陷并且在耗时和内存上有问题,再加上当时的硬件不能强大到提供可接受的性能。OTI成功地为Smalltalk-80和其它Smalltalk实现提供了一套原生的组件工具集。这些用户体验方面的成果被用来构建第一版的SWT。VisualAge Micro版和SWT取得了成功,所以当Eclipse启动的时候,选择SWT是很自然的事情。在Eclipse中使用SWT而不是Swing分化了Java社区。尽管这被有些人视为阴谋,但是Eclipse取得了成功并且使用SWT也能将其与其它Java程序分别开来。Eclipse高效、像素完美,所以一般人都会惊叹:“我不能相信这是Java程序”。

早期的Eclipse SDK只能运行在Linux和Windows上。到2010年的时候,它已经支持十多个平台了。开发人员可以为一个平台开发应用,然后将其部署到多个平台之上。在当时的Java社区,为Java语言开发一个新的组件工具库是有争议的一件事,但是Eclipse的提交者们觉得在桌面上为用户提供最好的原生体验是值得投入的一件事情。这个主张在今天得到了验证,现在有数百万行依赖于SWT的代码。

JFace是构建在SWT之上用来提供通用UI编码任务的一层(库),例如框架需要的首选项或向导等。与SWT一样,它可以用在很多的可视化系统中,但它是用纯Java实现的并不包含任何本地系统代码。

平台还提供了一个集成的帮助系统,这个系统基于名为主题的小信息单元来构建。话题是由一个标签(label)及其目标地址的引用。这个目标可以是描述附加链接的HTML文档文件或XML文档。主题按照目录(table of content,TOC)组织在一起。可以将主题理解为叶子,而目录是组织的枝干。为了给你的应用添加帮助内容,可以实现org.eclipse.help.toc,就像org.eclipse.platform.doc.isv的plugin.xml这样:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>

<!-- ===================================================================== -->
<!-- Define primary TOC                                                    -->
<!-- ===================================================================== -->
  <extension
     point="org.eclipse.help.toc">
  <toc
        file="toc.xml"
        primary="true">
  </toc>
  <index path="index"/>
  </extension>
<!-- ===================================================================== -->
<!-- Define TOCs                                                           -->
<!-- ===================================================================== -->
  <extension
     point="org.eclipse.help.toc">
  <toc
        file="topics_Guide.xml">
  </toc>
  <toc
        file="topics_Reference.xml">
  </toc>
  <toc
        file="topics_Porting.xml">
  </toc>
  <toc
        file="topics_Questions.xml">
  </toc>
  <toc
        file="topics_Samples.xml">
  </toc>
  </extension>

Eclipse使用Apache Lucene来实现索引和搜索在线帮助内容。在早期的Eclipse版本中,在线帮助一个Tomcat web应用来提供。另外,通过在Eclipse内部提供帮助,你还可以利用帮助插件来提供一个独立的帮助服务器。

Eclipse还提供了团队合作工作的支持以实现与源码库交互、创建补丁以及其它通用的任务。工作空间以文件和元数据集合的形式提供了存储在文件系统上的工作内容。它还提供了一个调试器来跟踪Java代码中的问题以及构建特定语言调试器的平台。

Eclipse项目的一个目标就是鼓励这个技术的开源和商业用户来扩展平台是满足其需求,而鼓励这样做的一种方式就是提供稳定的API。API可以视为明确应用行为的一个技术协议。它也可以被视为一个社会契约。在Eclipse项目中,其理念为:“API是永远的”(API is forever)。所以,在设计API时需要仔细考虑因为它可能被无限期地使用。稳定的API就是客户端或API用户与提供者的一个契约。这个契约要保证客户端能够长期依赖Eclipse平台所提供的API,而不需要在客户端进行痛苦的重构。而好的API还需要足够灵活以允许实现能够不断改进。

6.1.2 Java开发工具(JDT)

JDT提供了Java编辑器、向导、重构支持、调试、编译器以及增量构建功能。这个编译器也用来内容辅助、导航以及其它编辑特性。Eclipse并没有包含Java SDK,因此取决于用户选择安装哪个SDK。为什么JDT团队选择实现一个编译器来编译Eclipse中的Java代码呢?最初,他们从VisualAge Micro版本贡献的代码中得到了一个编译器。他们计划基于这个编译器来构建工具,所以编写这个编译器就成为了一个合乎逻辑的决定。这种方式还允许JDT的贡献者提交扩展点来扩展编译器。如果这个编译器是由第三方提供的命令行应用就会比较困难了。

编写自己编译器的方式提供了在IDE内部增量构建的机制。增量构建能够有更高的性能,因为它只会重新编译修改的及其依赖的文件。这个增量编译器是如何实现的呢?当你在Eclipse中创建了一个Java工程时,你同时也在工作空间中创建了存储文件的资源。Eclipse内部的编译器利用你工作空间中的输入(.java文件)创建出输出(.class文件)。通过构建状态,构建器能够知道工作空间中的类型(类或接口)以及它们之间的引用关系。构建状态在编译器编译每个资源文件时提供给构建器。当一次增量构建触发时,构建器会得到资源的增量变化,它描述了所有新增、修改或删除的文件。删除的资源文件会将其对应的class文件删除。新增或修改的类型将会添加到一个队列中。队列中的文件将会按顺序编译并与原来的class文件进行对比以确定是否有结构性的变化。结构性的变化能够影响引用它的其它类型。例如,修改了方法签名或者添加或移除方法。如果存在结构性的变化,所有引用它的类型都要加入队列。如果所有的类型都已经修改,新生成的class文件被写入构建输出目录。构建状态随着编译类型的引用信息而变化。这个过程会对所有的类型重复进行一直到队列为空。如果存在编译错误,Java编辑器会创建问题标记。这些年来,JDT提供的工具随着Java运行环境新版本的变化也得到了极大的扩展。

6.1.3 插件开发环境(PDE)

插件开发环境提供了开发、构建、部署、测试插件及其它扩展Eclipse功能工件(artifact)的工具。因为在Java领域,Eclipse插件是一个新的工件类型,因此没有将其从源码转换成插件的构建系统。所以PDE团队开发了名为PDE构建器的组件来检查插件的依赖并生成构建该工件的Ant脚本。