开源软件架构-Eclipse

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

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

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释放版本。

早期的Eclipse

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

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

开源软件架构-Eclipse

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

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文件,你能看到指定了名字和版本号以及它需要从其它插件导入的依赖。

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

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

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

开源软件架构-Eclipse

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

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

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

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

开源软件架构-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这样:

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

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

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

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运行环境新版本的变化也得到了极大的扩展。

插件开发环境(PDE)

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

Eclipse 3.0:运行时,RCP和Robots

运行时

鉴于在发布周期的一系列重大变化,Eclipse 3.0可能是最重要的释放版本。在3.0之前的Eclipse架构中,Eclipse由插件构成的组件模型在互相交互上有两种方式。首先,通过在它们的plugin.xml中使用requires语句来表达依赖。如果插件A依赖插件B,按照Java类的可见性约定,插件B中的所有Java类和资源对插件A来说都是可见的。每个插件都会有一个版本号,它们也可以指定依赖的版本号。其次,组件模型提供了扩展和扩展点机制。历史上,Eclipse的提交者为Eclipse SDK编写了自己的运行环境来管理类加载器、插件依赖以及扩展和扩展点。

Equinox 在Eclipse中最初是一个孵化项目。Equinox 的目标是取代已有的Eclipse组件模型,并提供对动态插件的支持。纳入考虑的方案包括JMX、Jakarta Avalon以及OSGi。鉴于JMX并不是成熟的组件模型,所以不是合适的方案。没有选择Jakarta Avalon是因为它作为一个项目已经失去了发展的势头。除了技术需要,支持这些技术的社区也同等重要。他们是否会愿意接受Eclipse选定的变化?是否能够得到积极的发展和更广泛的接受?Equinox 团队认为他们最终所选择技术的社区与技术考量本身一样重要。

在研究和评估可行的选择后,提交者选择了OSGi。为什么是OSGi?它有一个语义化的版本模式来管理依赖。它提供了JDK本身所缺乏的模块化框架。对其它bundle可见的包需要明确进行导出,而其它的将会被隐藏。OSGi提供了自己的类加载器,所以Equinox 团队不需要再维护自己的了。通过标准化Eclipse生态系统之外那些已被广泛采用的组件模型,他们认为会吸引到更广泛的社区支持并且Eclipse会被更多的采用。

Equinox 团队对OSGi充满活力的社区感到满意,他们可以与这个社区合作来实现Eclipse需要的组件模型功能。例如,当时的OSGi只支持在包级别列出依赖并不支持Eclipse需要的插件级别。另外,OSGi当时还没有片段(fragment)的理念,而这是Eclipse为已存在的插件在某平台或环境上提供特定代码的机制。例如,提供运行在Linux或Windows文件系统上的片段以及提供语言翻译的片段。一旦确定采用OSGi作为新的运行环境,提交者需要一个开源的框架实现。他们评估了Oscar(Apache Felix的前身)以及IBM开发的服务管理框架(Service Management Framework,SMF)。当时,Oscar是一个没有被广泛采用的研究项目。他们最终选择了SMF,因为它已经用在一些产品上并达到了企业应用的水准。Equinox实现现在是OSGi规范的参考实现。

为了保证已有的插件能够在3.0安装环境中依旧好用,Eclipse提供了一个兼容层。如果为了适应3.0底层架构的变化而要求开发者重写他们的插件,那将会影响到Eclipse作为一个工具平台的发展势头。Eclipse消费者的期望是这个平台依旧好用。

切换到OSGi后,Eclipse的插件被称为bundle。插件和bundle是一回事。他们都提供了一个模块化的功能子集并在manifest中包含了子描述的元数据信息。在之前,依赖、导出包以及扩展和扩展点都在plugin.xml中进行描述。改为OSGi的bundle后,扩展和扩展点还是在plugin.xml中进行描述,因为它们是Eclipse的概念。其它的信息在OSGi版本的bundle manifest文件META-INF/MANIFEST.MF中进行描述。为了适应这种变化,PDE在Eclipse中提供了一个新的manifest编辑器。每个bundle都有名字和版本。org.eclipse.ui这个bundle的manifest如下:

manifest还能指定bundle需要的执行环境(bundle required execution environment,BREE)。执行环境指定了bundle运行所需要的最低Java环境信息。Java编译器并不能理解bundle和OSGi manifest。PDE提供了开发OSGi bundle的工具。所以,PDE解析bundle的manifest并生成bundle的classpath。如果在你的manifest中声明了J2SE-1.4的执行环境,然后编写一些包含注解的代码的话,那在你的代码中将会提示编译错误。这能够保证你的代码遵循你在manifest中声明的协议。

OSGi为Java提供了一个模块化框架。OSGi框架管理一系列子描述的bundle及其类加载机制功能。每个bundle都有自己的类加载器。对于bundle来说,其可见的类路径是通过检查其manifest的依赖构建的。因此,manifest描述了bundle导出的包,这些包对客户端可见就像公共API对调用者可见一样。使用这些API的bundle必须相应地导入需要包。另外,manifest允许声明依赖的版本。看一下上面manifest中的Require-Bundle信息,你会发现org.eclipse.ui依赖的org.eclipse.core.runtime bundle的版本必须大于等于3.2.0并且小于4.0.0。

开源软件架构-Eclipse

OSGi是一个动态的框架,它支持bundle的安装、启动、停止和卸载。正如前面提到的,Eclipse的懒激活是很重要的优势因为插件的类只有在需要的时候才会被加载。OSGi的bundle生命周期也能实现这种方式。当你启动OSGi应用,bundle处于已安装状态。如果依赖条件满足,bundle会变为已处理的状态(resolved state)。一旦处于已处理状态,这个bundle中的类就能够加载并执行了。启动中状态意味着bundle按照其激活策略正在被激活。一旦被激活后,bundle处于活跃状态(active state),它此时能够获取需要的资源并与其它bundle交互。当bundle执行启动器(activator)的stop方法来清理在活跃状态中开启的资源时,bundle处于正在停止状态(stopping state)。最后,bundle可以被卸载,这意味着它不可用了。

随着API的发展,需要有一种手段来明确告知用户发生了变化。一个可行的方案就是对bundle使用语义化的版本并在manifest中明确依赖的版本范围。OSGi使用四部分的版本命名模式,如图:

开源软件架构-Eclipse

基于OSGi的版本命名模式,每个bundle有一个名字和四部分版本号所组成的唯一标示。对用户来讲,id和版本号组合起来代表了一组唯一的字节。按照Eclipse的惯例,如果对bundle进行了修改,用户根据版本号某一部分的变化能够判断了变化的类型。因此,如果你想表示API的破坏性变化,你要增加第一部分(主版本)的值。如果你只是增加API,你需要增加第二部分(小版本)的值。如果只是修改缺陷不影响API,需要增加第三部分(服务版本)的值。最后,第四部分或所谓的限定部分用来表示基于源码控制库的构建id。

除了能够指定bundle间的固定依赖,OSGi还有一套服务(service)的机制,它支持bundle间进一步解耦合。服务也是对象,它会把一些属性注册在OSGi服务注册器中。不同于扩展点,服务是动态注册的,而扩展点是在Eclipse启动的时候通过扫描bundle注册到扩展点注册器中的。需要使用服务的bundle需要将定义服务协议的包导入进来,框架根据服务注册器来确定使用哪个服务实现。

就像Java类文件中的主方法,会有一个特殊的应用来定义Eclipse的启动。Eclipse应用的通过扩展点来定义。例如,启动Eclipse IDE本身的应用是org.eclipse.ui.ide.workbench,它是在org.eclipse.ui.ide.application中定义的:

Eclipse提供了很多的应用,例如运行独立帮助服务器的,Ant任务的以及JUnit测试的等。

富客户端平台(Rich Client Platform,RCP)

开源社区工作的最有意思的一件事就是用户可以以你完全预想不到的方式来使用软件。Eclipse的初衷是提供一个平台和工具来创建和扩展IDE。但是,在3.0版本要发布的时候,从缺陷报告来看,社区用户有人用了平台bundle中的一部分来构建富客户端平台(RCP)应用。因为Eclipse原来是以IDE为中心的视角来创建的,它需要做一些重构来允许社区用户更便利地应用于这种场景。RCP应用不需要IDE相关的功能,所以为了让社区用户构建RCP应用,他们将几个bundle分离了出来并组成了一个更小的集合。

开源软件架构-Eclipse

看一下图6.6的架构,Eclipse运行环境依旧提供应用模型和扩展注册。插件模型之间的依赖关系通过OSGi来进行管理。用户除了能够扩展Eclipse来得到自己的IDE以外,他们还能够基于RCP应用框架来构建更通用的应用。

Eclipse 3.4

人们认为很容易地更新应用到新版本或添加新的包容是理所应当的。Firefox无缝地做到了这一点。对于Eclipse来讲,曾经这不是一件容易的事。最初用来为Eclipse添加新内容或更新版本的机制是更新管理器(Update Manager)。

为了理解更新和安装操作会有什么变化,有必要理解Eclipse的特性(feature)概念为何物。对于Eclipse来讲,特性是一个PDE的工件,它定义了以特定格式打包在一起的一组bundle并且能够构建和安装。特性可以包含其它的特性(见图6.7)。

开源软件架构-Eclipse

如果你只想更新Eclipse中的某一个bundle到新版本,整个特性需要被更新,因为这是更新管理器所采用的粗粒度机制。为了一个bundle而更新特性是低效的。

在工作空间中,你可以使用PDE向导来创建并构建特性。文件feature.xml定义了特性中包含的bundle以及bundle的一些简单属性。像bundle一样,特性也有名字和版本。特性可以包含其它的特性,并且可以指定其所包含特性的版本范围。包含在特性中的bundle会被罗列出来并附带一些属性。例如,你可以查看片段org.eclipse.launcher.gtk.linux.x86_64指定了它所使用的操作系统(os)、窗口系统(ws)以及架构(arch)。所以,当更新到新版本的时候,这个片段只能安装在这个平台上。这些平台相关的过滤条件包含在bundle的OSGi manifest中。

Eclipse应用不仅包含特性和bundle。还有平台相关的执行文件来启动Eclipse自身、许可证文件以及平台相关的类库,就像以下列表中的Eclipse应用中包含的文件。

这些文件不能通过更新管理器来更新,同样是因为它只能处理特性。鉴于这些文件在每个主版本释放的时候都会更新,这就意味着每当有新版本的时候,用户必须下载一个新的zip包而不是更新已有的安装。这对于Eclipse社区来讲是难以接受的。PDE支持通过产品文件来指明构建RCP应用需要的所有文件。但是,更新管理器并没有一种机制将这些文件自动提供到你的安装程序中,这让用户和产品开发人员都很沮丧。在2008年3月,p2作为新的提供方案(provisioning solution)放到了SDK中。为了向后兼容,更新管理器依旧可用,但是默认启动的是p2。

p2的理念

Equinox p2完全是关于安装单元的(installation unit,IU)。IU是要安装工件的id和名字的描述。这个元数据也描述了工件的功能(提供了什么)和需求(它的依赖)。如果工件只用于特定的环境,元数据也能表达适用范围的过滤信息。例如,org.eclipse.swt.gtk.linux.x86片段只能用于Linux gtk x86机器。从根本上来讲,元数据就是bundle的manifest信息的表达。而工件是要安装的二进制位。通过分离元数据和它所描述的工件,实现了关注点的分离。p2仓库需要包含元数据和工件库。

开源软件架构-Eclipse

概要文件(profile)是本地安装程序的IU列表。例如,Eclipse SDK会有一个概要文件来描述当前的安装情况。对于Eclipse来说,你可以将其更新到一个新版本,这将会创建包含不同UI的新概要文件。概要文件还会包含安装的相关属性如操作系统、窗口系统以及架构参数。概要参数还会保存安装目录和位置。概要文件通过一个注册器来管理,而注册器能存储多个概要文件。指令(director)负责触发内容提供操作。它与规划器和引擎协同工作。规划器检查已有的概要文件并确定更新安装程序的必需操作。引擎负责负责真正的内容提供操作并将工件安装到磁盘上。Touchpoint是引擎的一部分,它会与要安装系统的运行时环境协同工作。例如,对于Eclipse SDK,Eclipse Touchpoint能够了解怎样安装bundle。对于Linux系统来说,Eclipse是通过RPM二进制文件安装的,引擎就会使用一个RPM touchpoint。同样,p2能够在一个进程内部进行安装也可以用独立的进程来进行安装,就像系统构建那样。

新的p2内容提供系统有很多的好处。Eclipse的安装工件可以基于释放版本不断更新。因为前一版本的概要文件存储在磁盘上,所以能够将Eclipse恢复到以前的安装状态。另外,给以概要文件和仓库,你能够在你的机器创建一个相同的Eclipse以重现用户所报告的缺陷。基于p2的内容提供系统不仅能够安装和更新Eclipse SDK,它还能用于RCP和OSGi用例。Equinox团队还与另一个Eclipse项目即Eclipse通信框架(Eclipse Communication Framework,ECF)合作,该项目为从p2仓库中获取工件和元数据提供可靠的网络传输。

当p2发布到SDK中的时候,社区中有很多讨论。鉴于更新管理器对Eclipse安装内容来讲并不是一个最优的方案,Eclipse的用户通常都会讲bundle解压放到安装目录中然后重启Eclipse。这种方式能够比较好的处理bundle。这也意味着你安装程序中的(插件)冲突是在运行时处理的,而不是在安装的时候。这种(bundle间的)约束关系应该在安装的时候解决而不是在运行时。但是,用户通常意识不到这些问题,他们会以为既然这些bundle在磁盘上,他们就应该能够正常工作。以前,Eclipse提供的更新站点只是一个包含jar形式bundle和特性的目录。文件site.xml提供了用户在站点上可用的特性。伴随着p2的出现,在p2仓库中提供的元数据信息也更复杂了。为了创建元数据,构建过程需要有些变化,你可以在构建时创建元数据也可以基于已有的bundle运行一个单独的创建任务来生成元数据。起初之时,没有足够的文档来描述这些变化。同时,和往常一样,将一项新技术实现提供给广大的用户群体时,会有各种预想不到的缺陷需要处理。但是,通过编写更完善的文档以及花费大量时间来修正缺陷,Equinox团队已经解决了这些问题而且p2已经是很多商用产品的底层内容提供引擎。同时,Eclipse基金会每年都使用p2仓库将所有的Eclipse社区贡献项目发布出来。

Eclipse 4.0

架构必须持续地进行检查以评估其是否依然合适。它是否能引入新的技术?它是否能带动社区的成长?它是否便于吸收新的提交者?在2007年底,Eclipse项目确定这些问题的答案是否定的所以他们着手设计新版本的Eclipse。同时,他们意识到有成千上万个Eclipse项目依赖于已有的API。在2008年的时候,他们创建了一个新的孵化项目,这个项目有三个明确的目标:通过开放式的架构来简化Eclipse开发模型、吸引新的开发者以及利用基于web技术的优势。

开源软件架构-Eclipse

Eclipse 4.0首次发布于2010年7月,它针对于早期试用者以获取反馈信息。它的SDK包含两部分的bundle,一部分来源于3.6释放版,还有一部分新的bundle来源于技术项目。像3.0一样,有一个兼容层以保证现存的bundle在新版本中依然可用。一如既往的是,要警告用户为了保证兼容性需要使用公开的API。如果你的bundle使用了内部代码,将无法保证兼容性。4.0版本提供的Eclipse 4应用平台提供了如下的特性:

模型工作台

在4.0版本中,提供了一个模型工作台,它使用了Eclipse模型框架(Eclipse Modeling Framework,EMFgc)。鉴于渲染器需要与模型交互并生成SWT代码,有必要在模型和渲染视图之间进行关注点的分离。默认会使用SWT渲染器,但是其它的方案也是可行的。如果你创建了一个4.x的示例应用,会为默认的渲染模型创建一个XMI文件。模型可以进行修改,而工作台会随着模型的变化即时更新。图6.10是4.0的一个示例应用所生成的模型。

开源软件架构-Eclipse

级联样式表样式

Eclipse发布于2001年,早于富互联网应用的时代,在这个时代可以使用CSS来实现皮肤的切换以提供不同的外观和体验。Eclipse4.0提供了使用样式表来容易地修改Eclipse应用外观和体验的功能。默认的CSS样式单可以在org.eclipse.platform bundle的css文件下找到。

依赖注入

Eclipse的扩展注册器和OSGi服务都是基于服务模型编程的。按照惯例,服务编程模型包含服务的生产者和消费者。而居间者(broker)负责管理生产者和消费者的关系。

开源软件架构-Eclipse

在传统的Eclipse3.4.x应用中,消费者需要了解为了使用服务需要了解实现类的位置并理解其在框架中的继承关系。这样消费者的代码可重用性就降低了因为不能重写消费者所能接受到的服务。例如,如果你想在Eclipse 3.x中更新状态栏中的信息,代码应该是这样的:

Eclipse 3.6是基于组件构建的,但是很多的组件耦合性很高。为了能够耦合更宽松的组件来组建应用,Eclipse4.0使用了依赖注入来为客户端提供给服务。Eclipse4.x中的依赖注入是通过使用一个自定义的框架来实现的,这个框架使用了上下文的理念来提供一个通用的机制来为消费者定位服务。上下文存在于应用和框架之间。上下文是有等级的。如果一个上下文的请求不能得到满足,它将会把这个请求委托给父上下文。Eclipse的上下文,名为IEclipseContext,存储了可用的服务并提供了OSGi服务的查找。简单来说,上下文类似于一个Java的map,里面提供了一个名字或类与对象之间的映射。上下文处理模型元素和服务。每个模型元素都有一个上下文。在Eclipse 4.x中,服务是以OSGi服务的机制进行发布的。

开源软件架构-Eclipse

生产者将服务和对象添加到上下文中储存。通过上下文,服务被注册到消费者对象中。消费者生命需要的服务而上下文负责确定如何满足这个需求。这种方式使得使用动态服务更容易了。在Eclipse 3.x中,消费者需要注册监听器,当服务可用或不可用的时候获取通知。在Eclipse 4.x中,一旦一个上下文注入到消费者对象中,任何变化都会自动传递到那个对象中。换句话说,依赖注入会再次发生。消费者通过使用Java 5的注解来声明使用上下文,这些注解是符合JSR 330规范的如@inject,除此以外还会有一些自定义的Eclipse注解。构造器、方法以及域注入都是支持的。4.x的运行环境会扫描对象来寻找这些注解。实际执行的操作取决于使用的注解。

分离上下文和应用使得组件能够更好地常用,也使得服务的消费者免于理解内部实现。在4.x中,更新状态行的代码如下:

应用服务

Eclipse 4.0的一个主要目标是简化用户使用的API以便于其实现通用的服务。简单的服务列表被称为“20件事”(the twenty things)或Eclipse应用服务。其目标是为用户提供单独的API使得用户不必深入了解所有的API。它们被组织成独立的服务,因此可以用于其它非Java语言,像JavaScript。例如,有这样的API可以访问应用模型,读取和修改首选项以及报告错误和警告。

结论

基于组件化架构的Eclipse可以不断吸收新的技术而同时保证向后的兼容性。这样的成本比较高昂,但是回报在于Eclipse社区在不断发展壮大,因为用户能够基于建立起来的信任,不断使用这些稳定的API开发产品。 Eclipse广大的用户群体会有不同的使用场景而我们众多的API使得新的用户很难适应和理解。回顾过去,我们应该让API更简单一些。如果80%的用户只使用20%的API,有必要对其进行简化,这也是Eclipse 4.x创建的原因之一。

聪明的用户群体带来了有趣的使用场景,例如分解出IDE中的bundle来构建RCP应用。另一方面,群体有时候也会创造一些噪音来要求实现很少见的场景,这消耗了大量的时间来实现。

在Eclipse项目的早期,提交者有充裕的时间来编写文档、样例以及回答社区的问题。随着时间的推移,这个任务转移给了整个Eclipse社区。我们本可以提供更好地文档和样例来帮助社区,但是因为每个释放版本都会有大量的特性使得这变得很困难。一般情况下,软件发布总是会延期;然而在Eclipse,我们总是按期发布,这样做同时也可以帮助我们的客户建立起按期发布产品的信心。

通过吸收新技术以及重新改造Eclipse的外观和运行机制,我们会持续与用户进行交流并使他们留在我们的社区。如果你对Eclipse相关信息感兴趣,请访问http://www.eclipse.org。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注