## 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的生命周期
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 版本命名模式
基于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架構
看一下圖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的特性層級
如果你只想更新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倉庫需要包含元數據和工件庫。

圖 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社區貢獻項目發布出來。
- 前言(卷一)
- 卷1:第1章 Asterisk
- 卷1:第3章 The Bourne-Again Shell
- 卷1:第5章 CMake
- 卷1:第6章 Eclipse之一
- 卷1:第6章 Eclipse之二
- 卷1:第6章 Eclipse之三
- 卷1:第8章 HDFS——Hadoop分布式文件系統之一
- 卷1:第8章 HDFS——Hadoop分布式文件系統之二
- 卷1:第8章 HDFS——Hadoop分布式文件系統
- 卷1:第12章 Mercurial
- 卷1:第13章 NoSQL生態系統
- 卷1:第14章 Python打包工具
- 卷1:第15章 Riak與Erlang/OTP
- 卷1:第16章 Selenium WebDriver
- 卷1:第18章 SnowFlock
- 卷1:第22章 Violet
- 卷1:第24章 VTK
- 卷1:第25章 韋諾之戰
- 卷2:第1章 可擴展Web架構與分布式系統之一
- 卷2:第1章 可擴展Web架構與分布式系統之二
- 卷2:第2章 Firefox發布工程
- 卷2:第3章 FreeRTOS
- 卷2:第4章 GDB
- 卷2:第5章 Glasgow Haskell編譯器
- 卷2:第6章 Git
- 卷2:第7章 GPSD
- 卷2:第9章 ITK
- 卷2:第11章 matplotlib
- 卷2:第12章 MediaWiki之一
- 卷2:第12章 MediaWiki之二
- 卷2:第13章 Moodle
- 卷2:第14章 NginX
- 卷2:第15章 Open MPI
- 卷2:第18章 Puppet part 1
- 卷2:第18章 Puppet part 2
- 卷2:第19章 PyPy
- 卷2:第20章 SQLAlchemy
- 卷2:第21章 Twisted
- 卷2:第22章 Yesod
- 卷2:第24章 ZeroMQ