Eclipse架构演进

Eclipse软件架构

Kim Moir
实现软件模块化是一项众所周知的艰巨任务。与由不同社区编写的庞大代码库的互操作性也很难管理。在Eclipse,我们在这两方面都取得了成功。2010 年 6 月,Eclipse 基金会发布了 Helios 协调版本,来自 40 多家公司的 39 个项目和 490 名提交者共同合作,在基础平台的功能基础上进行开发。Eclipse最初的架构愿景是什么?它是如何发展的?应用的架构如何起到鼓励社区参与和发展的作用?让我们回到最开始。
2001年11月7日,一个名为Eclipse 1.0的开源项目发布。当时,Eclipse被描述为"一个综合开发环境(IDE),适用于任何事物,也适用于任何具体事物"。这种描述是故意通用的,因为其架构愿景不仅仅是另一套工具,而是一个框架;一个模块化和可扩展的框架。Eclipse提供了一个基于组件的平台,可以作为开发人员构建工具的基础。这种可扩展的架构鼓励社区在核心平台的基础上,将其扩展到最初愿景的极限之外。Eclipse以平台的形式开始,Eclipse SDK是概念验证产品。Eclipse SDK允许开发者自我托管并使用Eclipse SDK本身来构建较新版本的Eclipse。
开放源码开发者的刻板印象是,一个利他主义者为了解决自己的个人利益,辛辛苦苦到深夜修复bug,实现奇妙的新功能。相反,如果你回顾Eclipse项目的早期历史,一些最初捐赠的代码是基于IBM开发的VisualAge for Java。第一批从事这个开源项目的提交者是IBM一家名为Object Technology International(OTI)的子公司的员工。这些投入者有偿全职参与这个开源项目,回答新闻组的问题,解决错误,实现新功能。一个由感兴趣的软件供应商组成的联盟成立了,以扩大这种开放工具的努力。Eclipse 联盟的最初成员是 Borland、IBM、Merant、QNX 软件系统公司、Rational 软件公司、RedHat、SuSE 和 TogetherSoft。
通过对这一努力的投资,这些公司将拥有基于Eclipse的商业产品的专业知识。这类似于企业对Linux内核的投资,因为让员工改进作为其商业产品基础的开源软件符合他们的自身利益。2004年初,Eclipse基金会成立,以管理和扩大不断发展的Eclipse社区。这个非营利性的基金会由企业会员会费资助,并由董事会管理。今天,Eclipse社区的多样性已经扩大到包括170多家成员公司和近1 000名承诺者。
最初,人们对"Eclipse"的认识仅仅是SDK,但如今它的意义远不止于此。2010年7月,eclipse.org上有250个不同的项目在开发。有支持用C/C++、PHP、Web服务、模型驱动开发、构建工具等开发的工具。每一个项目都包含在一个顶层项目(TLP)中,该项目由一个项目管理委员会(PMC)管理,该委员会由项目的资深成员提名,负责制定技术方向和发布目标。为了简洁起见,本章的范围将局限于Eclipse1和Runtime Equinox2项目中Eclipse SDK架构的演变。由于Eclipse有着悠久的历史,我将重点介绍早期的Eclipse,以及3.0、3.4和4.0版本。

  1. 早期的Eclipse
    在21世纪初,软件开发者的工具很多,但能一起工作的工具很少。Eclipse试图提供一个开源平台,为应用开发者创建可互操作的工具。这将允许开发人员专注于编写新的工具,而不是编写代码处理基础设施问题,如与文件系统交互,提供软件更新,并连接到源代码仓库。Eclipse最著名的可能是Java开发工具(JDT)。其意图是,这些模范的Java开发工具将成为有兴趣为其他语言提供工具的人的榜样。
    在深入了解Eclipse的架构之前,我们先来看看Eclipse SDK对开发者来说是什么样子的。启动Eclipse并选择工作台后,你会看到Java透视图。透视图组织了当前使用的工具所特有的视图和编辑器。
    Eclipse架构演进_第1张图片
  2. 图6.1:Java视角
    早期版本的Eclipse SDK架构有三大要素,对应着三大子项目:平台、JDT(Java开发工具)和PDE(插件开发环境)。
  3. 6.1.1. 平台
    Eclipse平台使用Java编写,运行它需要一个Java虚拟机。它是由称为插件的小功能单元构建的。插件是Eclipse组件模型的基础。插件本质上是一个JAR文件,它有一个manifest,该manifest描述了它本身、它的依赖关系以及它如何被利用或扩展。这个manifest信息最初存储在一个plug-in.xml文件中,它驻扎在插件目录的根目录下。Java开发工具提供了用Java开发的插件。插件开发环境(PDE)提供了开发插件以扩展Eclipse的工具。Eclipse插件是用Java编写的,但也可以包含非代码贡献,如在线文档的HTML文件。每个插件都有自己的类加载器。插件可以通过在plugin.xml中使用request语句来表达对其他插件的依赖。观察org.eclipse.ui插件的plugin.xml,可以看到它的名称和指定的版本,以及它需要从其他插件导入的依赖关系。
<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>
   <requirements>
      <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"/>requirements>
plugin>

为了鼓励人们在Eclipse平台的基础上进行构建,需要有一种机制来为平台做出贡献,并让平台接受这种贡献。这是通过使用扩展和扩展点来实现的,扩展点是Eclipse组件模型的另一个元素。出口确定了你希望别人在编写他们的扩展时使用的接口,这就限制了你的插件之外的类可以使用到那些被导出的类。它还对插件外部可用的资源提供了额外的限制,而不是将所有公共方法或类都提供给消费者。导出的插件被认为是公共API。所有其他的都被认为是私有的实现细节。要编写一个能为Eclipse工具栏贡献菜单项的插件,可以使用org.eclipse.ui插件中的actionSets扩展点。

<extension-point id="actionSets" name="%ExtPoint.actionSets"
                 schema="schema/actionSets.exsd"/>
<extension-point id="command" name="%ExtPoint.command"
                 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="schemma/dropActions.exsd"/>

你的插件扩展可以为org.eclipse.ui.actionSet扩展点贡献一个菜单项,它看起来像这样。

<plugin
   id="com.example.helloworld"
   name="com.example.helloworld"
   版本="1.0.0"><runtime>
      <library name="helloworld.jar"/>runtime>
   <requires>
      <import plugin="org.eclipse.ui"/>requires>
   <extension
         point="org.eclipse.ui.actionSets"><actionSet
            label="示例行动集"
            visible="true"
            id="org.eclipse.helloworld.actionSet"><menu
               label="示例&菜单"
               id="exampleMenu">
            <separator
                  name="exampleGroup">
            separator>
         menu>
         <action
               label="&示例动作"
               icon="icon/example.gif"
               tooltip="你好,Eclipse世界"
               class="com.example.helloworld.actions.ExampleAction"
               menubarPath="exampleMenu/exampleGroup"
               toolbarPath="exampleGroup"
               id="org.eclipse.helloworld.actions.ExampleAction">action>
      actionSet>
   extension>
plugin>

当Eclipse启动时,运行时平台会扫描安装中插件的清单,并建立一个存储在内存中的插件注册表。扩展点和相应的扩展通过名称进行映射。产生的插件注册表可以从Eclipse平台提供的API中引用。该注册表被缓存到磁盘上,以便在下次重新启动Eclipse时可以重新加载这些信息。所有插件在启动时都会被发现以填充注册表,但在实际使用代码之前,它们不会被激活(加载类)。这种方法被称为"懒惰激活"。通过在需要之前不实际加载与插件相关的类,可以减少在安装中添加额外捆绑包对性能的影响。例如,贡献给org.eclipse.ui.actionSet扩展点的插件,在用户选择工具栏中的新菜单项之前不会被激活。
在这里插入图片描述

  • 图 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构造函数就会被运行,然后初始化一个工作台动作委托。由于工作台中的选择已经发生了变化,并且已经创建了委托人,所以可以改变动作。打开消息对话框,显示"你好,Eclipse架构世界"。
这种可扩展的架构是Eclipse生态系统成功发展的关键之一。公司或个人可以开发新的插件,并将其作为开放源码发布或进行商业销售。
关于Eclipse最重要的一个概念是,一切都是插件。无论是Eclipse平台中包含的插件,还是你自己编写的插件,插件都是组装好的应用程序的一级组件。图6.3显示了Eclipse早期版本中插件所贡献的相关功能集群。
Eclipse架构演进_第2张图片

  • 图6.3:早期的Eclipse架构
    工作台是Eclipse平台用户最熟悉的UI元素,因为它提供了组织Eclipse在桌面上如何显示给用户的结构。工作台由视角、视图和编辑器组成。编辑器与文件类型相关联,因此当打开一个文件时,就会启动正确的编辑器。视图的一个例子是"问题"视图,它指示Java代码中的错误或警告。编辑器和视图共同构成了一个视角,它以一种有组织的方式向用户展示工具。
    Eclipse工作平台是建立在标准Widget Toolkit(SWT)和JFace基础上的,SWT值得探讨一下。小工具工具箱一般分为原生的和仿真的两种。原生的widget工具包使用操作系统调用来构建用户界面组件,如列表和按钮。与组件的交互由操作系统处理。仿真的widget工具包在操作系统之外实现组件,自己处理鼠标和键盘、绘图、焦点和其他widget功能,而不是依赖操作系统。两种设计都有不同的优缺点。
    原生小部件工具包是"完美的像素"。它们的小部件的外观和感觉与桌面上其他应用程序中的对应物一样。操作系统供应商不断地改变他们的小组件的外观和感觉,并添加新的功能。原生小组件工具包可以免费获得这些更新。不幸的是,本地工具箱很难实现,因为它们的底层操作系统小部件实现有很大的不同,导致不一致和程序不可移植。
    仿真部件工具包要么提供自己的外观和感觉,要么试图像操作系统一样绘制和行为。它们比原生工具包的最大优势是灵活性(尽管现代原生小组件工具包如Windows Presentation Framework (WPF)也同样灵活)。因为实现小组件的代码是工具包的一部分,而不是嵌入到操作系统中,所以可以让小组件以任何方式绘制和行为。使用仿真小组件工具箱的程序具有高度的可移植性。早期的仿真小组件工具箱名声不佳。它们的速度通常很慢,而且在模拟操作系统方面做得很差,使得它们在桌面上显得格格不入。尤其是当时的Smalltalk-80程序,由于使用了仿真部件,很容易被识别。用户知道他们运行的是一个"Smalltalk程序",这影响了人们对Smalltalk应用程序的接受。
    与其他计算机语言(如C和C++)不同,Java的第一个版本带有一个本地的widget工具箱库,称为抽象窗口工具箱(AWT)。AWT被认为是有限的、错误的和不一致的,受到了广泛的谴责。在Sun公司和其他地方,部分原因是由于使用AWT的经验,一个可移植的、高性能的原生widget工具包被认为是不可行的。解决办法是Swing,一个功能齐全的仿真部件工具包。
    1999年左右,OTI使用Java实现了一个名为VisualAge Micro Edition的产品。VisualAge Micro Edition的第一个版本使用了Swing,而OTI使用Swing的经验并不乐观。早期版本的Swing存在bug,有定时和内存问题,而且当时的硬件功能不够强大,无法提供可接受的性能。OTI曾成功地为Smalltalk-80和其他Smalltalk实现构建了一个本地widget工具包,以获得Smalltalk的认可。这个经验被用来构建SWT的第一个版本。VisualAge Micro Edition和SWT获得了成功,当Eclipse的工作开始时,SWT自然而然地成为了选择。在Eclipse中使用SWT而不是Swing,这在Java社区中引起了分裂。有些人认为是阴谋,但Eclipse是成功的,SWT的使用使它与其他Java程序区别开来。Eclipse性能卓越,像素完美,人们普遍的感受是,“我不敢相信这是一个Java程序”。
    早期的Eclipse SDK在Linux和Windows上运行。在2010年,已经支持了十多个平台。开发者可以为一个平台编写应用程序,并将其部署到多个平台上。为Java开发一个新的widget工具包在当时的Java社区内是一个有争议的问题,但Eclipse的提交者认为,为了在桌面上提供最好的原生体验,这是值得的。这一论断在今天依然适用,有数百万行代码依赖于SWT。
    JFace是SWT之上的一个层,它为常见的UI编程任务提供了工具,例如偏好和向导的框架。像SWT一样,它被设计成可以与许多窗口系统一起工作。然而,它是纯Java代码,不包含任何本地平台代码。
    该平台还提供了一个基于称为主题的小信息单位的综合帮助系统。一个主题由一个标签和对其位置的引用组成。这个位置可以是一个HTML文档文件,也可以是一个描述附加链接的XML文档。主题以目录(TOC)的形式分组。将主题视为树叶,将TOC视为组织的分支。要在你的应用程序中添加帮助内容,你可以向org.eclipse.help.toc扩展点做出贡献,就像下面org.eclipse.platform.doc.isv plugin.xml所做的那样。


<plugin>




   <extension
         point="org.eclipse.help.toc">
      <toc
            file="toc.xml"
            primary="true">
      toc>
      <index path="index"/>
   extension>



   <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>

Apache Lucene 被用来索引和搜索在线帮助内容。在Eclipse的早期版本中,在线帮助是作为Tomcat Web应用程序提供的。此外,通过在Eclipse本身内部提供帮助,也可以使用帮助插件的子集来提供独立的帮助服务器。3
Eclipse还提供了团队支持,以与源代码库进行交互,创建补丁和其他常见任务。工作空间提供了文件和元数据的集合,将你的工作存储在文件系统中。还有一个调试器来跟踪Java代码中的问题,以及一个构建特定语言调试器的框架。
Eclipse项目的目标之一是鼓励该技术的开源和商业消费者扩展该平台以满足他们的需求,而鼓励这种采用的方法之一是提供一个稳定的API。一个API可以被认为是一个技术合同,规定了你的应用程序的行为。它也可以被认为是一种社会契约。在Eclipse项目中,口号是"API是永远的"。因此,鉴于API是要无限期使用的,在编写API时必须仔细考虑。一个稳定的API是客户端或API消费者与提供者之间的合同。这种合同确保了客户端可以长期依赖Eclipse平台提供API,而不需要客户端进行痛苦的重构。一个好的API也是足够灵活的,可以让实现不断发展。

  1. 6.1.2.Java开发工具(JDT)
    JDT提供了Java编辑器、向导、重构支持、调试器、编译器和增量构建器。编译器还用于内容辅助、导航和其他编辑功能。Eclipse中并没有附带Java SDK,所以由用户自己选择在桌面上安装哪个SDK。为什么JDT团队要单独编写一个编译器来在Eclipse中编译你们的Java代码?他们有一个最初的编译器代码贡献,来自VisualAge Micro Edition。他们计划在编译器之上构建工具,因此自己编写编译器是一个合理的决定。这种方法也允许JDT提交者提供扩展点来扩展编译器。如果编译器是由第三方提供的命令行应用程序,这将是困难的。
    编写自己的编译器提供了一种机制,在集成开发环境中为增量构建器提供支持。增量构建器提供了更好的性能,因为它只重新编译已经改变的文件或其依赖关系。增量构建器是如何工作的?当您在Eclipse中创建一个Java项目时,您是在工作区中创建资源来存储您的文件。Eclipse内的构建器在你的工作空间内接收输入(.java文件),并创建一个输出(.class文件)。通过构建状态,构建器知道工作空间中的类型(类或接口),以及它们如何相互引用。每次编译源文件时,编译器都会向构建器提供构建状态。当调用增量构建时,构建器会得到一个资源delta,它描述了任何新的、修改的或删除的文件。删除的源文件会删除其对应的类文件。新的或修改的类型被添加到队列中。队列中的文件按顺序编译,并与旧的类文件进行比较,以确定是否有结构性变化。结构性改变是对类的修改,会影响到引用它的另一个类型。例如,改变方法签名,或者添加或删除一个方法。如果有结构变化,所有引用它的类型也会被添加到队列中。如果类型有任何改变,新的类文件会被写入构建输出文件夹。构建状态会随着编译类型的引用信息而更新。这个过程对队列中的所有类型重复进行,直到空为止。如果有编译错误,Java编辑器会创建问题标记。多年来,随着Java运行时本身的新版本,JDT提供的工具已经得到了极大的扩展。
  2. 6.1.3.插件式开发环境(PDE)
    插件开发环境(PDE)提供了开发、构建、部署和测试插件以及其他用于扩展Eclipse功能的工件的工具。由于Eclipse插件在Java世界中是一种新的工件类型,所以没有一个构建系统可以将源代码转化为插件。因此,PDE团队编写了一个名为PDE Build的组件,它可以检查插件的依赖关系,并生成Ant脚本来构建构建工件。
  3. 6.2.Eclipse 3.0。运行时、RCP和机器人
  4. 6.2.1.运行时
    Eclipse 3.0可能是最重要的Eclipse版本之一,因为在这个发布周期中发生了许多重大变化。在3.0之前的Eclipse架构中,Eclipse组件模型由插件组成,这些插件可以通过两种方式相互交互。首先,它们可以通过使用插件.xml中的request语句来表达它们的依赖关系。如果插件A需要插件B,插件A就可以看到B的所有Java类和资源,尊重Java类的可见性约定。每个插件都有一个版本,他们也可以指定其依赖的版本。其次,组件模型提供了扩展和扩展点。历史上,Eclipse提交者为Eclipse SDK编写了自己的运行时来管理类加载、插件依赖和扩展及扩展点。
    Equinox项目是作为Eclipse的一个新的孵化器项目创建的。Equinox项目的目标是用现有的Eclipse组件模型取代Eclipse组件模型,并为动态插件提供支持。正在考虑的解决方案包括JMX、Jakarta Avalon和OSGi。JMX不是一个完全开发的组件模型,所以被认为不合适。没有选择Jakarta Avalon是因为它作为一个项目似乎正在失去动力。除了技术要求之外,考虑支持这些技术的社区也很重要。他们是否愿意加入Eclipse特有的变化?是否积极开发并获得新的采用者?Equinox团队认为,围绕他们最终选择的技术的社区与技术考虑同样重要。
    在研究和评估了现有的备选方案后,提交人选择了OSGi。为什么选择OSGi?它有一个语义版本管理方案来管理依赖关系,它提供了JDK本身所缺乏的模块化框架。它提供了一个JDK本身所缺乏的模块化框架。对其他捆绑包可用的包必须显式导出,而所有其他的包都是隐藏的。OSGi提供了自己的类加载器,因此Equinox团队不必继续维护自己的类。通过标准化一个在Eclipse生态系统之外有更广泛采用的组件模型,他们认为他们可以吸引更广泛的社区,并进一步推动Eclipse的采用。
    Equinox团队认为,由于OSGi已经有了一个现有的、充满活力的社区,他们可以与该社区合作,帮助将Eclipse所需的功能包含在组件模型中。例如,当时OSGi只支持在包级别上列出需求,而不是Eclipse所要求的插件级别。此外,OSGi还没有包含片段的概念,片段是Eclipse为现有插件提供平台或环境特定代码的首选机制。例如,片段提供了与Linux和Windows文件系统一起工作的代码,以及贡献语言翻译的片段。一旦决定使用OSGi作为新的运行时,提交者需要一个开源的框架实现。他们评估了Oscar,Apache Felix的前身,以及IBM开发的服务管理框架(SMF)。当时,Oscar是一个研究项目,部署有限。SMF最终被选中,因为它已经被用于航运产品,因此被认为是企业级的。Equinox的实现作为OSGi规范的参考实现。
    还提供了一个兼容层,以便现有的插件在3.0的安装中仍然可以使用。要求开发人员重写他们的插件以适应Eclipse 3.0基础架构的变化,会使Eclipse作为一个工具平台的势头停滞不前。Eclipse消费者的期望是,这个平台应该继续工作。
    随着向 OSGi 的转换,Eclipse 插件被称为 bundle。插件和bundle是一样的:它们都提供了一个模块化的功能子集,在manifest中用元数据描述自己。以前,依赖关系、导出的包以及扩展和扩展点都是在plugin.xml中描述的。随着转向OSGi bundles,扩展和扩展点继续在plugin.xml中描述,因为它们是Eclipse的概念。其余的信息则在META-INF/MANIFEST.MF中描述,这是OSGi的捆绑清单版本。为了支持这一变化,PDE在Eclipse中提供了一个新的清单编辑器。每个bundle都有一个名称和版本。org.eclipse.ui bundle的manifest是这样的。
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name:%Plugin.name
Bundle-SymbolicName: org.eclipse.ui; singleton:=true。
Bundle-Version: 3.3.0.qualifier.
Bundle-ClassPath: .
Bundle-Activator: org.eclipse.ui.internal.UIPlugin。
Bundle-Vendor: %Plugin.providerName.
捆绑-本地化:插件
Export-Package: org.eclipse.ui.internal;x-internal:=true。
Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.2.0,4.0.0)"。
org.eclipse.swt;bundle-version="[3.3.0,4.0.0)";可见性:=reexport。
org.eclipse.jface;bundle-version="[3.3.0,4.0.0)";可见性:=reexport。
org.eclipse.ui.workbench;bundle-version="[3.3.0,4.0.0)";visibility:=reexport.bundle-version="[3.3.0,4.0.0)"。
org.eclipse.core.expressions;bundle-version="[3.3.0,4.0.0)"
Eclipse-LazyStart: true
Bundle-RequiredExecutionEnvironment:CDC-1.0/Foundation-1.0, J2SE-1.3

从Eclipse 3.1开始,清单还可以指定 bundle所需的执行环境(BREE)。执行环境指定了 bundle 运行所需的最小 Java 环境。Java编译器不理解捆绑包和OSGi清单。PDE提供了开发OSGi bundle的工具。因此,PDE 会解析 bundle 的清单,并为该 bundle 生成 classpath。如果你在manifest中指定了J2SE-1.4的执行环境,然后写了一些包含generics的代码,你会被告知你的代码中存在编译错误。这可以确保你的代码遵守你在清单中指定的合同。
OSGi为Java提供了一个模块化框架。OSGi框架管理自描述捆绑的集合,并管理它们的类加载。每个捆绑包都有自己的类加载器。捆绑包可用的classpath是通过检查manifest的依赖关系来构造的,并生成捆绑包可用的classpath。OSGi应用程序是捆绑的集合。为了充分拥抱模块化,你必须能够以可靠的格式为消费者表达你的依赖关系。因此,manifest描述的是这个bundle的客户端可用的导出包,它对应的是可供消费的公共API。正在消费该API的捆绑包必须有一个对应的他们正在消费的包的导入。manifest还允许你表达你的依赖的版本范围。看上面清单中的Require-Bundle标题,你会注意到org.eclipse.core.runtime bundle所依赖的org.eclipse.ui必须至少是3.2.0,小于4.0.0。
Eclipse架构演进_第3张图片

  • 图 6.4:OSGi Bundle Lifecycle
    OSGi是一个动态框架,它支持安装、启动、停止或卸载捆绑包。如前所述,懒惰激活是Eclipse的核心优势,因为插件类在需要之前不会被加载。OSGi bundle生命周期也实现了这种方法。当你启动一个OSGi应用时,捆绑包处于安装状态。如果它的依赖性得到满足,该捆绑就会变为解析状态。一旦解决,该捆绑中的类就可以被加载和运行。启动状态意味着该 bundle 正在根据其激活策略被激活。一旦激活,该捆绑处于激活状态,它就可以获取所需的资源并与其他捆绑进行交互。当一个捆绑正在执行它的激活器停止方法,以清理它在激活时打开的任何资源时,它就处于停止状态。最后,一个bundle可以被卸载,这意味着它不能使用。
    随着API的发展,需要有一种方法来向您的消费者发出变化信号。其中一种方法是在您的清单中使用捆绑包和版本范围的语义版本化来指定依赖关系的版本范围。OSGi使用了一个四部分的版本命名方案,如图6.5所示。
    Eclipse架构演进_第4张图片
  • 图6.5:版本命名方案
    在OSGi版本编号方案中,每个捆绑包都有一个由名称和四部分版本号组成的唯一标识符。一个id和版本号一起对消费者来说表示了一组独特的字节。按照Eclipse的惯例,如果你要对一个bundle进行修改,版本号的每一段都向消费者表示所做的修改类型。因此,如果你想表明你打算打破API,你会递增第一个(主要)段。如果你刚刚添加了API,你就递增第二个(小)段。如果你修复了一个不影响API的小bug,则递增第三个(服务)段。最后,第四段或限定段递增,表示构建id源控制仓库标签。
    除了表达捆绑之间的固定依赖关系外,OSGi内部还有一种机制叫做服务,它提供了捆绑之间的进一步解耦。服务是具有一组属性的对象,这些属性在OSGi服务注册表中注册。与Eclipse在启动过程中扫描捆绑时在扩展注册表中注册的扩展不同,服务是动态注册的。正在消费服务的捆绑包需要导入定义服务合同的包,框架从服务注册表中确定服务的实现。
    就像Java类文件中的主方法一样,有一个特定的应用程序定义来启动Eclipse。Eclipse应用程序是用扩展名来定义的。例如,启动Eclipse IDE本身的应用程序是org.eclipse.ui.ide.workbench,它定义在org.eclipse.ui.ide.application bundle中。
<plugin>
    <extension
         id="org.eclipse.ui.ide.workbench"
         point="org.eclipse.core.runtime.applications">
      <application>
         <run
               class="org.eclipse.ui.internal.ide.application.IDEApplication">
         run>
      application>
  extension>
plugin>

Eclipse提供了许多应用程序,如运行独立的帮助服务器、Ant任务和JUnit测试的应用程序。

  • 6.2.2.富客户端平台(RCP)
    在开源社区工作最有趣的事情之一是,人们以完全意想不到的方式使用软件。Eclipse的初衷是提供一个平台和工具来创建和扩展IDE。然而,在3.0发布之前的一段时间里,bug报告显示,社区正在使用平台捆绑的子集,并使用它们来构建富客户端平台(RCP)应用程序,许多人都会将其识别为Java应用程序。由于Eclipse最初是以IDE为中心构建的,因此必须对捆绑包进行一些重构,以使这种用例更容易被用户社区采用。RCP应用程序并不需要IDE中的所有功能,所以几个捆绑包被拆分成了更小的捆绑包,可以被社区用于构建RCP应用程序。
    RCP在野外应用的例子包括:美国航天局在喷气推进实验室使用RCP监测火星车机器人,Bioclipse用于生物信息学的数据可视化,荷兰铁路公司用于监测火车性能。贯穿这些应用的共同点是,这些团队决定可以利用RCP平台提供的实用性,并集中精力在其上构建自己的专用工具。他们可以通过专注于在一个具有稳定API的平台上构建他们的工具来节省开发时间和资金,以保证他们的技术选择能够得到长期的支持。
    Eclipse架构演进_第5张图片
  • 图6.6: Eclipse 3.0架构
    从图6.6的3.0架构来看,你会注意到Eclipse Runtime仍然存在,以提供应用模型和扩展注册表。管理组件之间的依赖关系,插件模型现在由OSGi管理。除了继续能够为自己的IDE扩展Eclipse之外,消费者还可以在RCP应用框架的基础上建立更通用的应用。
  • 6.3.Eclipse 3.4
    轻松地将应用程序更新到新版本并添加新内容的能力是理所当然的。在Firefox中,它可以无缝地发生。对于Eclipse来说,它并不那么容易。更新管理器是最初的机制,用于在Eclipse安装中添加新内容或更新到新版本。
    要了解更新或安装操作过程中的变化,有必要了解Eclipse所说的"特性"是什么意思。特性是一个PDE构件,它定义了一组捆绑包,这些捆绑包以一种可以构建或安装的格式打包在一起。特性也可以包括其他特性。(见图 6.7)。
    Eclipse架构演进_第6张图片
  • 图6.7.Eclipse 3.3 SDK功能层次结构图
    如果你希望更新你的Eclipse安装到一个只包含一个新捆绑包的新版本,那么整个功能都必须更新,因为这是更新管理器使用的粗粒度机制。更新一个功能来修复一个捆绑包是低效的。
    有PDE向导来创建功能,并在你的工作空间中构建它们。feature.xml文件定义了包含在特性中的捆绑包,以及捆绑包的一些简单属性。一个特性,就像一个捆绑包,有一个名称和一个版本。特性可以包含其他特性,并为它们包含的特性指定版本范围。一个特性中包含的捆绑包,以及特定的属性都会被列出。例如,你可以看到org.eclipse.launcher.gtk.linux.x86_64片段指定了它应该使用的操作系统(os)、窗口系统(ws)和架构(arch)。因此升级到新版本时,这个片段将只安装在这个平台上。这些平台过滤器包含在这个捆绑包的OSGi清单中。

<feature
      id="org.eclipse.rcp"
      label="%featureName"
      version="3.7.0.qualifier"
      provider-name="%providerName"
      plugin="org.eclipse.rcp"
      image="eclipse_update_120.jpg">

   <description>
      %description
   description>

   <copyright>
      %copyright
   copyright>

   <license url="%licenseURL">
      %license
   license>

   <plugin
         id="org.eclipse.equinox.launcher"
         download-size="0"
         install-size="0"
         version="0.0.0"
         unpack="false"/>

   <plugin
         id="org.eclipse.equinox.launcher.gtk.linux.x86_64"
         os="linux"
         ws="gtk"
         arch="x86_64"
         download-size="0"
         install-size="0"
         version="0.0.0"
         fragment="true"/>

一个Eclipse应用程序不仅仅由功能和捆绑包组成。有启动Eclipse本身的特定平台可执行文件、许可证文件和特定平台的库,如这个Eclipse应用程序中包含的文件列表所示。

com.ibm.icu
org.eclipse.core.commands
org.eclipse.core.conttenttype
org.eclipse.core.databinding
org.eclipse.core.databinding.beans
org.eclipse.core.expressions
org.eclipse.core.jobs
org.eclipse.core.runtime
org.eclipse.core.runtime.compatibility.auth
org.eclipse.equinox.common
org.eclipse.equinox.launcher
org.eclipse.equinox.launcher.carbon.macosx
org.eclipse.equinox.launcher.gtk.linux.ppc
org.eclipse.equinox.launcher.gtk.linux.s390
org.eclipse.equinox.launcher.gtk.linux.s390x
org.eclipse.equinox.launcher.gtk.linux.x86
org.eclipse.equinox.launcher.gtk.linux.x86_64

这些文件不能通过更新管理器更新,因为它只处理功能。由于这些文件中的许多文件在每一个主要的版本都会更新,这意味着每次有新的版本发布时,用户都必须下载一个新的压缩包,而不是更新他们现有的安装。这对Eclipse社区来说是不可接受的。PDE提供了对产品文件的支持,它指定了构建Eclipse RCP应用程序所需的所有文件。然而,更新管理器并没有一个机制来提供这些文件到你的安装中,这对用户和产品开发者来说都是非常令人沮丧的。2008年3月,p2作为新的供应解决方案被发布到SDK中。为了向后兼容,Update Manager仍然可以使用,但p2是默认启用的。

  • 6.3.1. p2概念
    Equinox p2是关于安装单元(IU)的。IU是对你要安装的工件的名称和ID的描述。这个元数据也描述了工件的能力(提供了什么)和需求(它的依赖性)。元数据还可以表达适用性过滤器,如果一个工件只适用于特定的环境。例如,org.eclipse.swt.gtk.linux.x86片段只有在Linux gtk x86机器上安装时才适用。从根本上说,元数据是捆绑清单中信息的表达。人造物只是被安装的二进制位。通过分离元数据和它们所描述的工件来实现关注点的分离。一个p2资源库由元数据和工件资源库组成。
    Eclipse架构演进_第7张图片

  • 图6.8:P2概念
    配置文件是您安装中的 IU 列表。例如,您的 Eclipse SDK 有一个描述您当前安装的 profile。在 Eclipse 中,您可以请求更新到较新版本的构建,这将创建一个具有不同 IU 集的新配置文件。配置文件还提供了与安装相关的属性列表,例如操作系统、窗口系统和架构参数。配置文件还存储了安装目录和位置。配置文件由一个配置文件注册表持有,该注册表可以存储多个配置文件。导演负责调用供应操作。它与规划师和引擎一起工作。规划师检查现有的配置文件,并确定将安装转化为新状态所必须进行的操作。引擎负责执行实际的供应操作,并将新工件安装到磁盘上。触点是引擎的一部分,与被安装系统的运行时实现一起工作。例如,对于Eclipse SDK,有一个Eclipse接触点,它知道如何安装捆绑包。对于从RPM二进制文件中安装Eclipse的Linux系统,引擎会处理一个RPM接触点。此外,p2还可以在进程内或进程外的单独进程中执行安装,例如构建。
    新的p2供应系统有很多好处。Eclipse安装工件可以从一个版本更新到另一个版本。由于以前的配置文件存储在磁盘上,所以也有办法恢复到以前的Eclipse安装。此外,给定一个配置文件和一个存储库,你可以重新创建报告错误的用户的Eclipse安装,尝试在自己的桌面上重现问题。使用p2提供了一种更新和安装的方法,而不仅仅是Eclipse SDK,它是一个适用于RCP和OSGi用例的平台。Equinox团队还与另一个Eclipse项目的成员合作,即Eclipse Communication Framework(ECF),为消费p2仓库中的工件和元数据提供可靠的传输。
    当p2发布到SDK中时,Eclipse社区内有许多热烈的讨论。由于update manager对于Eclipse安装来说是一个不太理想的解决方案,Eclipse消费者习惯于将捆绑包解压缩到他们的安装中,然后重新启动Eclipse。这种方法在尽力的基础上解决了你的捆绑包。这也意味着你安装中的任何冲突都是在运行时解决的,而不是在安装时解决。约束应该在安装时解决,而不是在运行时解决。然而,用户往往对这些问题熟视无睹,并认为既然捆绑包存在于磁盘上,那么它们就可以工作。以前,Eclipse提供的更新站点是一个由JARred bundles和特性组成的简单目录。一个简单的site.xml文件提供了站点中可供消耗的特性的名称。随着p2的出现,p2仓库中提供的元数据要复杂得多。为了创建元数据,需要对构建过程进行调整,以便在构建时生成元数据,或者在现有的捆绑包上运行一个生成器任务。起初,缺乏描述如何进行这些改变的文档。同样,如同往常一样,向更多的人展示新技术会暴露出意想不到的bug,这些bug必须得到解决。然而,通过编写更多的文档和长时间的工作来解决这些bug,Equinox团队能够解决这些问题,现在p2是许多商业产品背后的底层供应引擎。以及,Eclipse基金会每年都会使用所有贡献项目的p2聚合仓库来运送其协调发布。

  • 6.4.6.4. Eclipse 4.0
    必须不断地检查架构,以评估它是否仍然合适。它是否能够纳入新技术?它是否能鼓励社区的发展?它是否容易吸引新的贡献者?2007年末,Eclipse项目的提交者们决定,这些问题的答案都是否定的,他们开始为Eclipse设计新的愿景。同时,他们意识到,有成千上万的Eclipse应用依赖于现有的API。2008年底,一个孵化器技术项目应运而生,有三个具体目标:简化Eclipse编程模型,吸引新的提交者,使该平台能够利用新的网络技术,同时提供一个开放的架构。
    Eclipse架构演进_第8张图片

  • 图6.9:Eclipse 4.0 SDK早期采用者版本
    Eclipse 4.0于2010年7月首次发布,供早期采用者提供反馈。它由3.6版本中的SDK捆绑包和技术项目中的新捆绑包组成。和3.0一样,有一个兼容层,这样现有的捆绑包就可以和新版本一起使用。和以往一样,有一个警告,即消费者需要使用公共API,以确保兼容性。如果你的捆绑包使用的是内部代码,就没有这样的保证。4.0版本提供了Eclipse 4应用平台,它提供了以下功能。

  • 6.4.1.模型工作台
    在4.0中,使用Eclipse建模框架(EMFgc)生成了一个模型工作台。模型和视图的渲染之间的关注点是分离的,因为渲染器与模型对话,然后生成SWT代码。默认情况下是使用SWT渲染器,但也可以使用其他解决方案。如果您创建一个4.x应用程序示例,将为默认的工作台模型创建一个XMI文件。该模型可以被修改,工作台将被立即更新以反映模型的变化。图 6.10 是为 4.x 示例应用程序生成模型的一个例子。
    Eclipse架构演进_第9张图片

  • 图6.10:为例4.x应用生成的模型。

  • 6.4.2.层叠样式表的样式
    Eclipse发布于2001年,当时还没有出现丰富的互联网应用程序时代,这些应用程序可以通过CSS提供不同的外观和感觉的皮肤。Eclipse 4.0提供了使用样式表来轻松改变Eclipse应用程序的外观和感觉的能力。默认的CSS样式表可以在org.eclipse.platform bundle的css文件夹中找到。
    6.4.3.依赖性注入
    Eclipse扩展注册表和OSGi服务都是服务编程模型的例子。按照惯例,一个服务编程模型包含服务生产者和消费者。经纪人负责管理生产者和消费者之间的关系。
    Eclipse架构演进_第10张图片

  • 图6.11:生产者和消费者之间的关系
    传统上,在Eclipse 3.4.x应用中,消费者需要知道实现的位置,并了解框架内的继承,才能消费服务。因此,消费者代码的可重用性较差,因为人们无法重写消费者接收哪种实现。例如,如果你想在Eclipse 3.x中更新状态行上的消息,代码会是这样的。
    getViewSite().getActionBars().getStatusLineManager().setMessage(msg);
    Eclipse 3.6是由组件构建的,但这些组件中的许多组件耦合得太紧。为了组装更松耦合的组件的应用程序,Eclipse 4.0使用依赖注入来为客户提供服务。在Eclipse 4.x中,依赖注入是通过使用一个自定义框架来实现的,该框架使用上下文的概念作为一种通用机制来为消费者定位服务。上下文存在于应用程序和框架之间。上下文是有层次的。如果一个上下文有一个不能满足的请求,它将把这个请求委托给父上下文。Eclipse上下文被称为IEclipseContext,存储可用的服务,并提供OSGi服务查询。基本上,上下文类似于Java地图,它提供了一个名称或类到对象的映射。上下文处理模型元素和服务。模型的每个元素,都会有一个上下文。在4.x中,服务是通过OSGi服务机制来发布的。
    Eclipse架构演进_第11张图片

  • 图6.12:服务经纪人上下文
    生产者将服务和对象添加到存储它们的上下文中。服务由上下文注入到消费者对象中。消费者声明它想要什么,上下文决定如何满足这个请求。这种方法使消费动态服务变得更加容易。在Eclipse 3.x中,当服务可用或不可用时,消费者必须附加监听器才能得到通知。在Eclipse 4.x中,一旦上下文被注入到消费者对象中,任何变化都会再次自动传递给该对象。换句话说,依赖注入再次发生。消费者通过使用遵守 JSR 330 标准的 Java 5 注解(如 @inject)以及一些自定义的 Eclipse 注解来表示它将使用上下文。支持构造函数、方法和字段注入。4.x运行时会扫描对象以寻找这些注解。执行的操作取决于找到的注解。
    这种将上下文和应用之间的关注点分离,可以更好地重用组件,并免除消费者对实现的理解。在4.x中,更新状态行的代码是这样的。

@Inject
IStatusLineManager statusLine;
⋮    ⋮    ⋮
statusLine.setMessage(msg);
  1. 6.4.4.应用服务
    Eclipse 4.0 的主要目标之一是简化消费者的 API,以便易于实现通用服务。这些简单的服务列表后来被称为"二十件事",被称为Eclipse应用服务。其目标是提供独立的API,客户可以使用,而不必深入了解所有可用的API。它们被结构化为独立的服务,因此它们也可以用Java以外的其他语言来使用,比如Javascript。例如,有一个API可以访问应用程序模型,读取和修改偏好,并报告错误和警告。
  2. 6.5.6.5. 结论
    Eclipse的基于组件的架构已经发展到在保持向后兼容性的同时融入新技术。这样做的代价很高,但回报是Eclipse社区的发展,因为建立了信任,消费者可以继续基于稳定的API来发布产品。
    Eclipse有这么多的消费者,他们的用例多种多样,我们庞大的API变得很难让新消费者采用和理解。回想起来,我们应该让我们的API更简单。如果80%的消费者只使用了20%的API,那么就需要简化,这也是创建Eclipse 4.x流的原因之一。
    众人的智慧确实揭示了一些有趣的用例,比如将IDE分解成可用于构建RCP应用的捆绑包。相反,众人往往会产生大量的噪音,要求提供需要大量时间来实现的边缘案例方案。
    在Eclipse项目的早期,提交人可以奢侈地将大量时间投入到文档、示例和回答社区问题上。随着时间的推移,这个责任已经转移到了整个Eclipse社区。我们本可以更好地提供文档和用例来帮助社区,但考虑到每个版本都有大量的项目计划,这很难做到。与人们对软件发布日期滑落的期望相反,在Eclipse,我们始终如一地按时交付我们的版本,这让我们的消费者相信他们也能做到这一点。
    通过采用新技术和重塑Eclipse的外观和工作方式,我们继续与消费者对话,并让他们参与到社区中来。如果您有兴趣参与Eclipse,请访问http://www.eclipse.org。
    脚注
  3. http://www.eclipse.org
  4. http://www.eclipse.org/equinox
  5. 例如:http://help.eclipse.org。

你可能感兴趣的:(笔记)