在他的最近的博文中,Glyn 介紹了 OSGi 的“uses”指令。在這篇博文中,我想更深入地探討 uses 約束衝突的原因,並提供一些診斷應用程式中 uses 問題的小貼士。
對於大多數示例,我將使用原始的 Equinox 而不是 dm Server。原因是 uses 約束並非特定於 dm Server,而是與所有 OSGi 使用者相關。在這篇博文的末尾,我將演示 dm Server 內建的一些智慧約束失敗診斷功能。
依賴約束不匹配
uses 衝突最常見的原因是一個或多個依賴約束不匹配。以以下三個 Manifest 為例
Manifest-Version: 1.0
Bundle-Name: Spring Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: spring
Bundle-Version: 2.5.5
Export-Package: spring.orm.hibernate;version="2.5.5";uses:="eclipselink"
Import-Package: eclipselink;version="[1.0, 2.0)"
Manifest-Version: 1.0
Bundle-Name: EclipseLink 1 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: eclipselink
Bundle-Version: 1
Export-Package: eclipselink;version="1.0.0"
Manifest-Version: 1.0
Bundle-Name: EclipseLink 2 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: eclipselink
Bundle-Version: 2
Export-Package: eclipselink;version="2.0.0"
在這裡,您可以看到一個 spring bundle 和兩個 eclipselink bundle。顯然,這些不是真實的 bundle。spring bundle 匯入了 eclipselink 包,範圍是 [1.0, 2.0)。顯然,只有 eclipselink_1 bundle 可以滿足這個約束。現在,考慮來自兩個不同應用程式的這些 Manifest
Manifest-Version: 1.0
Bundle-Name: App1 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: app1
Bundle-Version: 1.0.0
Import-Package: spring.orm.hibernate,eclipselink;version="[1.0, 1.0]"
Manifest-Version: 1.0
Bundle-Name: App2 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: app2
Bundle-Version: 1.0.0
Import-Package: spring.orm.hibernate,eclipselink;version="[2.0, 2.0]"
在這裡我們可以看到 app1 匯入了範圍為 [1.0, 1.0] 的 eclipselink,而 app2 匯入了範圍為 [2.0, 2.0] 的 eclipselink。如果我將這些 bundle 安裝到 Equinox 中,然後嘗試啟動 app bundle,控制檯將顯示類似以下內容
id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.0.v20080605-1900
2 RESOLVED spring_2.5.5
3 RESOLVED eclipselink_1.0.0
4 RESOLVED eclipselink_2.0.0
5 ACTIVE app1_1.0.0
6 INSTALLED app2_1.0.0
在這裡我們可以看到 spring 和 eclipselink bundle 都已解析。app1 bundle 已經啟動,但 app2 bundle 未啟動。要找出原因,我們可以使用 diag 命令
osgi> diag app2
file:/Users/robharrop/dev/resdiag/uses/app2/bin [6]
Package uses conflict: Import-Package: spring.orm.hibernate; version="0.0.0"
在這裡我們可以看到 app2 bundle 無法解析,因為它匯入的 spring.orm.hibernate 存在包 uses 衝突。這意味著 app2 中對 spring.orm.hibernate 的匯入無法滿足,因為它的其他匯入與 *可能* 提供 spring.orm.hibernate 的 bundle(在本例中是 spring bundle)上的 uses 約束髮生衝突。
診斷此問題的第一步是找出 spring.orm.hibernate bundle 的可能供應商。從我們的用例中可知,唯一可能的供應商是 spring bundle,但如果您不知道供應商,可以使用 packages 命令查詢
osgi> packages spring.orm.hibernate
spring.orm.hibernate; version="2.5.5"<file:/Users/robharrop/dev/resdiag/uses/spring/bin [2]>
file:/Users/robharrop/dev/resdiag/uses/app1/bin [5] imports
這表明 spring.orm.hibernate 包由 bundle 2 匯出。有了這些資訊,我們可以找出在 bundle 2 的 uses 指令中列出的 spring.orm.hibernate 包包含哪些包
osgi> headers 2
Bundle headers:
Bundle-ManifestVersion = 2
Bundle-Name = Spring Bundle
Bundle-SymbolicName = spring
Bundle-Version = 2.5.5
Export-Package = spring.orm.hibernate;version="2.5.5";uses:="eclipselink"
Import-Package = eclipselink;version="[1.0, 2.0)"
Manifest-Version = 1.0
在這裡,我們可以看到 uses 中唯一的包是 eclipselink 包,所以它肯定就是罪魁禍首。事實上,我們可以看到 Spring bundle 需要範圍為 [1.0, 2.0) 的 eclipselink,而 app2 需要範圍為 [2.0, 2.0] 的 eclipselink - 這兩個範圍互斥,這意味著 app2 *不能* 與 spring bundle 連線到相同版本的 eclipselink。
在 uses 列表很長的情況下,您可以透過找出哪些列出的包有多個供應商來縮小可能的衝突範圍。必須存在多個供應商才能出現 uses 約束衝突。
版本不匹配並非依賴約束不匹配的唯一原因。約束可能由於屬性以及版本而無法匹配。
安裝順序問題
如果我們回到前面的例子,並將 spring bundle 的 manifest 修改為可以接受 2.0 版本的 eclipselink 包,並放寬 app1 的範圍,使其可以接受高於 1.0 的任何版本,那麼應該可以解決問題
Manifest-Version: 1.0
Bundle-Name: Spring Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: spring
Bundle-Version: 2.5.5
Export-Package: spring.orm.hibernate;version="2.5.5";uses:="eclipselink"
Import-Package: eclipselink;version="[1.0, 2.0]"
Manifest-Version: 1.0
Bundle-Name: App1 Bundle
Bundle-ManifestVersion: 2
Bundle-SymbolicName: app1
Bundle-Version: 1.0.0
Import-Package: spring.orm.hibernate,eclipselink;version="1.0"
安裝 bundle 並啟動 app bundle 表明這一改變產生了很大的不同
id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.0.v20080605-1900
1 RESOLVED spring_2.5.5
2 RESOLVED eclipselink_1.0.0
3 RESOLVED eclipselink_2.0.0
4 ACTIVE app1_1.0.0
5 ACTIVE app2_1.0.0
現在兩個 app bundle 都可以啟動了。不幸的是,還有一個更微妙的問題等待著我們。取決於安裝順序,這組 bundle 仍然可能無法一起執行。為了說明這一點,讓我們將 spring、eclipselink_1 和 app1 作為一次事務安裝,然後啟動 app1
id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.0.v20080605-1900
1 RESOLVED spring_2.5.5
2 RESOLVED eclipselink_1.0.0
3 ACTIVE app1_1.0.0
現在,讓我們安裝 eclipselink_2 和 app2
id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.0.v20080605-1900
1 RESOLVED spring_2.5.5
2 RESOLVED eclipselink_1.0.0
3 ACTIVE app1_1.0.0
4 RESOLVED eclipselink_2.0.0
5 INSTALLED app2_1.0.0
應用程式 2 Bundle 不會啟動。來自 diag 的輸出告訴我們原因
osgi> diag app2
file:/Users/robharrop/dev/resdiag/uses/app2/bin [5]
Package uses conflict: Import-Package: spring.orm.hibernate; version="0.0.0"
uses 約束又出現了。按照上一節中確定的診斷步驟在這裡不會有幫助,因為沒有依賴約束不匹配 - 我們知道這一點,因為第一次這些 Bundle 都能正常解析。
這裡的問題是解析順序。Bundle 分成兩個不同的塊安裝和解析。第一個塊包括 spring、eclipselink_1 和 app1,第二個塊包括 eclipselink_2 和 app2。當第一個塊解析時(作為啟動 app1 Bundle 的結果),spring Bundle 將其匯入的 eclipselink 包連線到 eclipselink_1 Bundle。這可以透過控制檯進行確認
osgi> bundle app1
file:/Users/robharrop/dev/resdiag/uses/app1/bin [3]
Id=3, Status=ACTIVE Data Root=/opt/springsource-dm-server-1.0.0.RELEASE/lib/configuration/org.eclipse.osgi/bundles/3/data
No registered services.
No services in use.
No exported packages
Imported packages
spring.orm.hibernate; version="2.5.5"<file:/Users/robharrop/dev/resdiag/uses/spring/bin [1]>
eclipselink; version="1.0.0"<file:/Users/robharrop/dev/resdiag/uses/eclipselink1/bin [2]>
No fragment bundles
Named class space
app1; bundle-version="1.0.0"[provided]
No required bundles
請注意,匯入包部分顯示匯入的 eclipselink 版本 1.0.0 來自 eclipselink_1 bundle。當第二個塊安裝後,app2 bundle 無法解析,因為它需要版本 2.0.0 的 eclipselink,而 spring 已連線到版本 1.0.0 的 eclipselink。當所有 bundle 都作為一個塊安裝和解析時,OSGi 解析器會嘗試滿足 *所有* 約束,包括確保可以滿足 spring.orm.hibernate 上的 uses 約束。
要解決這個問題,我們不需要更改 Bundle。相反,我們可以將 Bundle 作為一塊重新安裝,或者我們可以觸發對 spring Bundle 的重新整理 - 實際上是要求 OSGi 重新執行解析過程。既然 eclipselink_2 Bundle 已安裝,我們可以期望得到不同的結果
osgi> refresh spring
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.0.v20080605-1900
1 RESOLVED spring_2.5.5
2 RESOLVED eclipselink_1.0.0
3 ACTIVE app1_1.0.0
4 RESOLVED eclipselink_2.0.0
5 ACTIVE app2_1.0.0
osgi> bundle spring
file:/Users/robharrop/dev/resdiag/uses/spring/bin [1]
Id=1, Status=RESOLVED Data Root=/opt/springsource-dm-server-1.0.0.RELEASE/lib/configuration/org.eclipse.osgi/bundles/1/data
No registered services.
No services in use.
Exported packages
spring.orm.hibernate; version="2.5.5"[exported]
Imported packages
eclipselink; version="2.0.0"<file:/Users/robharrop/dev/resdiag/uses/eclipselink2/bin [4]>
No fragment bundles
Named class space
spring; bundle-version="2.5.5"[provided]
No required bundles
請注意,重新整理 spring 使得 app2 bundle 得以解析。spring 中 eclipselink 包的連線已更改為由 eclipselink_2 bundle 匯出的版本 2.0.0 滿足。
dm Server 中的 Uses 約束
當您在 dm Server 中遇到 uses 約束衝突時,我們已經嘗試為您執行一些分析步驟,特別是識別可能不匹配的依賴約束
Could not satisfy constraints for bundle 'app2' at version '1.0.0'.
Cannot resolve: app2
Resolver report:
Bundle: app2_1.0.0 - Uses Conflict: Import-Package: spring.orm.hibernate; version="0.0.0"
Possible Supplier: spring_2.5.5 - Export-Package: spring.orm.hibernate; version="2.5.5"
Possible Conflicts: eclipselink
Uses 約束在企業庫中很常見,手動診斷故障可能是一場真正的噩夢。特別是當一個匯出的包在其 uses 子句中列出了 10 個或更多的包時,確定可能的衝突可能非常耗時。因此,自動化診斷是必須的,我希望不斷改進 dm Server 中的診斷程式碼,以便處理常見錯誤變得輕而易舉。
在下一個版本中,我們計劃將診斷工具直接內建到我們的 dm Server Eclipse 工具中,以便大多數這些問題將由 dm Server 自動診斷。