🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 6.2 Eclipse 3.0:运行时,RCP和Robots **6.2.1 运行时** 鉴于在发布周期的一系列重大变化,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-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 Bundle-Localization: plugin 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)";visibility:=reexport, org.eclipse.jface;bundle-version="[3.3.0,4.0.0)";visibility:=reexport, org.eclipse.ui.workbench;bundle-version="[3.3.0,4.0.0)";visibility:=reexpot, 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中,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。 ![图6.4 OSGi bundle的生命周期](http://box.kancloud.cn/2015-08-20_55d5840bb35a4.jpg) 图6.4 OSGi bundle的生命周期 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使用四部分的版本命名模式,如图6.5: ![图6.5 版本命名模式](http://box.kancloud.cn/2015-08-20_55d5840c2178d.jpg) 图6.5 版本命名模式 基于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中定义的: ~~~ <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 富客户端平台(Rich Client Platform,RCP)** 开源社区工作的最有意思的一件事就是用户可以以你完全预想不到的方式来使用软件。Eclipse的初衷是提供一个平台和工具来创建和扩展IDE。但是,在3.0版本要发布的时候,从缺陷报告来看,社区用户有人用了平台bundle中的一部分来构建富客户端平台(RCP)应用。因为Eclipse原来是以IDE为中心的视角来创建的,它需要做一些重构来允许社区用户更便利地应用于这种场景。RCP应用不需要IDE相关的功能,所以为了让社区用户构建RCP应用,他们将几个bundle分离了出来并组成了一个更小的集合。 ![图6.6 Eclipse 3.0架构](http://box.kancloud.cn/2015-08-20_55d5840c94c31.jpg) 图6.6 Eclipse 3.0架构 看一下图6.6的架构,Eclipse运行环境依旧提供应用模型和扩展注册。插件模型之间的依赖关系通过OSGi来进行管理。用户除了能够扩展Eclipse来得到自己的IDE以外,他们还能够基于RCP应用框架来构建更通用的应用。 ## 6.3 Eclipse 3.4 人们认为很容易地更新应用到新版本或添加新的包容是理所应当的。Firefox无缝地做到了这一点。对于Eclipse来讲,曾经这不是一件容易的事。最初用来为Eclipse添加新内容或更新版本的机制是更新管理器(Update Manager)。 为了理解更新和安装操作会有什么变化,有必要理解Eclipse的特性(feature)概念为何物。对于Eclipse来讲,特性是一个PDE的工件,它定义了以特定格式打包在一起的一组bundle并且能够构建和安装。特性可以包含其它的特性(见图6.7)。 ![图6.7 Eclipse 3.3 SDK的特性层级](http://box.kancloud.cn/2015-08-20_55d584131e7eb.jpg) 图6.7 Eclipse 3.3 SDK的特性层级 如果你只想更新Eclipse中的某一个bundle到新版本,整个特性需要被更新,因为这是更新管理器所采用的粗粒度机制。为了一个bundle而更新特性是低效的。 在工作空间中,你可以使用PDE向导来创建并构建特性。文件feature.xml定义了特性中包含的bundle以及bundle的一些简单属性。像bundle一样,特性也有名字和版本。特性可以包含其它的特性,并且可以指定其所包含特性的版本范围。包含在特性中的bundle会被罗列出来并附带一些属性。例如,你可以查看片段org.eclipse.launcher.gtk.linux.x86_64指定了它所使用的操作系统(os)、窗口系统(ws)以及架构(arch)。所以,当更新到新版本的时候,这个片段只能安装在这个平台上。这些平台相关的过滤条件包含在bundle的OSGi manifest中。 ~~~ <?xml version="1.0" encoding="UTF-8"?> <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应用不仅包含特性和bundle。还有平台相关的执行文件来启动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 ~~~ 这些文件不能通过更新管理器来更新,同样是因为它只能处理特性。鉴于这些文件在每个主版本释放的时候都会更新,这就意味着每当有新版本的时候,用户必须下载一个新的zip包而不是更新已有的安装。这对于Eclipse社区来讲是难以接受的。PDE支持通过产品文件来指明构建RCP应用需要的所有文件。但是,更新管理器并没有一种机制将这些文件自动提供到你的安装程序中,这让用户和产品开发人员都很沮丧。在2008年3月,p2作为新的提供方案(provisioning solution)放到了SDK中。为了向后兼容,更新管理器依旧可用,但是默认启动的是p2。 **6.3.1 p2的理念** Equinox p2完全是关于安装单元的(installation unit,IU)。IU是要安装工件的id和名字的描述。这个元数据也描述了工件的功能(提供了什么)和需求(它的依赖)。如果工件只用于特定的环境,元数据也能表达适用范围的过滤信息。例如,org.eclipse.swt.gtk.linux.x86片段只能用于Linux gtk x86机器。从根本上来讲,元数据就是bundle的manifest信息的表达。而工件是要安装的二进制位。通过分离元数据和它所描述的工件,实现了关注点的分离。p2仓库需要包含元数据和工件库。 ![p2的概念图](http://box.kancloud.cn/2015-08-20_55d58414a66df.jpg) 图 6.8 p2的概念图 概要文件(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社区贡献项目发布出来。