osgi java_使普通的旧Java OSGi兼容
osgi java
盡管OSGi在Java世界中越來越流行,但仍有許多Java應用程序和庫尚未設計成可在OSGi中使用。 有時您可能需要在OSGi環境中運行這樣的代碼,或者是因為您想利用OSGi本身提供的好處,或者因為您只需要此特定環境提供的某些功能。 通常,您負擔不起完全遷移到OSGi的負擔,或者至少需要一個過渡期,在此期間,您的代碼在OSGi內外均可以正常工作。 當然,您想以最小的努力做到這一點,而又不會增加軟件的復雜性。
最近,我們在SAP的團隊面臨著類似的挑戰。 我們有一個相當大的遺留純Java應用程序,多年來,它已發展為包括許多本地框架和自定義解決方案。 我們需要為此應用程序提供基于REST的接口,因此我們要么必須包含Web服務器,要么必須在具有該Web服務器的環境中運行。 我們選擇使用SAP Lean Java Server(LJS) ,后者是SAP NetWeaver Cloud背后的引擎,其中包括Tomcat和其他有用的服務。 但是,LJS基于Eclipse的OSGi實現Equinox ,因此我們需要確保我們的代碼與OSGi兼容,以確保流暢的互操作性。 在此過程中,我們學到了很多有關該主題的知識,因此我想在本文中與您分享我們最有趣的發現。
為了使純Java代碼在OSGi環境中順利運行,應滿足以下先決條件:
- 它打包為OSGi捆綁包 ,即具有有效OSGi清單的jar存檔。
- 它遵守OSGi對動態加載類施加的要求和限制。
- 它的所有軟件包僅由一個捆綁軟件導出,即沒有不同的捆綁軟件導出同一軟件包。
同樣,在許多情況下,您可能需要為要從OSGi控制臺啟動的應用程序創建新的入口點。 如果使用Equinox,則應考慮為此目的創建一個Equinox應用程序。
請注意,滿足以上要求并不意味著您不應該以任何方式將代碼遷移到OSGi,使其僅在OSGi中運行,也不意味著您應從根本上將開發環境或過程更改為基于OSGi。 相反,我們的經驗表明,通過以下列方式滿足上述要求,很可能實現與OSGi的兼容性而不會失去在OSGi外部運行的能力,并且不會顯著改變您的開發方法和工具:
- 可以使用BND和其他基于它的工具自動生成所有OSGi清單。 在OSGi之外,不使用這些清單,但也不會造成傷害。
- 基于Class.forName()動態加載類和自定義類加載可以由使用引擎蓋下的本機OSGi服務的幾乎相同的機制代替。 可以根據您的代碼是否在OSGi中執行而在原始和OSGi機制之間動態切換,而對現有代碼的更改很少。
- 另外,您可以通過使用OSGi服務機制動態注冊和發現“命名”實現來完全消除OSGi中的動態類加載。
- 由多個捆綁軟件導出的相同軟件包應該簡單地重命名。 顯然,這也適用于OSGi。
- 通過將所有特定于OSGi的代碼放在有限數量的包中,可以最大程度地減少對OSGi的依賴性,這些包最好也不包含應在OSGi外部執行的代碼。
以下各節提供有關如何實現此功能的更多詳細信息。
包裝為OSGi捆綁包
為了在OSGi環境中工作,所有Java代碼都應打包為OSGi捆綁包。 這不僅適用于由構建生成的所有存檔,還適用于作為軟件的一部分提供的所有依賴項。
如果您的構建使用Maven,則應強烈考慮使用Maven Bundle插件 (內部使用BND)為該構建生成的所有檔案生成有效的OSGi清單。 在大多數情況下,此插件的默認配置生成的清單將可以正常工作。 但是,在某些情況下,可能需要進行一些較小的調整和附加操作才能生成正確的清單,例如:
- 為僅通過反射使用的類添加其他導入包,因此BND無法找到。
- 為公開OSGi聲明性服務的分發包指定服務組件XML。
- 為依賴于自定義激活的捆綁軟件指定捆綁軟件激活器。
在我們的項目中,如下所述,在我們的父POM中配置了bundle插件:
<properties><classpath></classpath><import-package>*</import-package><export-package>{local-packages}</export-package><bundle-directives></bundle-directives><bundle-activator></bundle-activator><bundle-activationpolicy></bundle-activationpolicy><require-bundle></require-bundle><service-component></service-component>... </properties> ... <build><pluginManagement><plugins><plugin><groupId>org.apache.felix</groupId><artifactId>maven-bundle-plugin</artifactId><version>2.3.4</version><extensions>true</extensions><configuration><encoding>${project.build.sourceEncoding}</encoding><archive> <forced>true</forced> </archive><instructions><Bundle-SymbolicName>${project.artifactId}${bundle-directives}</Bundle-SymbolicName><Bundle-Name>${project.artifactId}</Bundle-Name><_nouses>true</_nouses><Class-Path>${classpath}</Class-Path><Export-Package>${export-package}</Export-Package><Import-Package>${import-package}</Import-Package><Bundle-Activator>${bundle-activator}</Bundle-Activator><Bundle-ActivationPolicy>${bundle-activationpolicy}</Bundle-ActivationPolicy><Require-Bundle>${require-bundle}</Require-Bundle><Service-Component>${service-component}</Service-Component></instructions></configuration><executions><execution><id>bundle-manifest</id><phase>process-classes</phase><goals><goal>manifest</goal></goals></execution></executions></plugin>...</plugins></pluginManagement> </build>在子POM中,我們指定任何需要具有與默認值不同的值的屬性。 在我們的案例中,這種POM相對較少。
我們的大多數依賴項也沒有OSGi清單,因此我們在構建過程中生成它們。 當前,這是通過使用BND wrap命令的Groovy腳本完成的。 對于我們的大多數依賴項,對清單使用通用模板就足夠了。 當前,我們使用以下模板,該模板由腳本動態生成:
Bundle-Name: ${artifactId} Bundle-SymbolicName: ${artifactId} Bundle-Version: ${version} -nouses: true Export-Package: com.sap.*;version=${version_space},* Import-Package: com.sap.*;version="[${version_space},${version_space}]";resolution:=optional,*;resolution:=optional 一世
僅在少數情況下,清單模板必須包含特定于混凝土罐的信息。 我們通過在SCM中提交這些模板并使用提交的版本而不是默認版本來捕獲此類細節。
符合OSGi類加載
常用的類加載機制的替代方法
OSGi環境強加了自己的類加載機制,以下文章中對此進行了詳細描述:
- OSGi類加載
- OSGi中的類加載和類型可見性
但是,一些普通的Java應用程序和庫通常廣泛依賴于創建自定義類加載器并通過Class.forName()或ClassLoader.loadClass()加載類,以便使用反射 ,而我們的應用程序就是其中之一。 這在OSGi中是有問題的,如《 OSGi準備就緒-加載類》中更詳細的描述。 本文提出的解決方案盡管有效,但不能直接應用于我們的案例,因為這將涉及大量更改舊代碼,這是我們目前不希望做的事情。
我們發現,可以完全依靠本機OSGi機制以優雅的方式解決此問題,對于我們的大部分遺留代碼都是透明的。 代替Class.forName() ,可以使用以下調用順序:
- 使用FrameworkUtil.getBundle()獲取當前的Bundle及其BundleContext 。
- 通過上一步中獲得的捆綁包上下文從OSGi服務注冊表中獲取標準PackageAdmin服務
- 使用PackageAdmin.getExportedPackage()和ExportedPackage.getExportingBundle()查找ExportedPackage.getExportingBundle() Bundle包。
- 最后,只需調用Bundle.loadClass()即可加載請求的類。
另外,盡管不可能直接使用低級捆綁軟件類加載器,但捆綁軟件類本身提供了諸如Bundle.loadClass()和Bundle.getResource()類的類加載方法。 因此,可以創建一個自定義的類裝入器,該類裝入器包裝一個包(或多個包)并委托給這些方法。
要使您的大部分遺留代碼在OSGi中工作,而只需進行少量更改,就可以通過以下方式對其進行調整:
- 如果代碼在OSGi中執行,則代替調用Class.forName() ,而調用實現上述序列的方法。
- 如果代碼在OSGi中執行, BundleClassLoader要從多個jar文件中創建自定義類加載器,而BundleClassLoader與這些jar文件相對應的BundleClassLoader包中創建BundleClassLoader 。
為了使上述更改更加直接,我們在應用程序中引入了一個名為ClassHelper的新類。 它是一個單例,提供以下靜態助手方法,這些方法委托給單個實例的相同非靜態方法:
public static boolean isOsgi(); public static Object getBundleContext(Class<?> clazz); public static Class<?> forName(String className, ClassLoader cl) throws ClassNotFoundException; public static ClassLoader getBundleClassLoader(String[] bundleNames, ClassLoader cl);這些方法的默認實現在基本ClassHelper類中實現了默認的非OSGi行為– isOsgi()返回false, getBundleContext()和getBundleClassLoader()返回null, forName()只是委托給Class.forName() 。
OsgiClassHelper類繼承自ClassHelper ,進而實現上述正確的OSGi行為。 我們將此類放在其自己的特殊捆綁包中,以確保包含ClassHelper和大量其他實用程序的捆綁包不受OSGi依賴關系的影響。 這個特殊的捆綁有一個Activator ,它取代了默認ClassHelper用一個實例OsgiClassHelper在束激活實例。 由于激活代碼僅在OSGi中執行,因此可以確保在兩種情況下均加載正確的實現。
在我們的其余代碼中, ClassHelper.forName()替換Class.forName()調用,并使用ClassHelper.forName()創建自定義類加載器就ClassHelper.getBundleClassLoader() 。
使用OSGi服務
在許多普通的Java應用程序中,某些實現是基于字符串“ handle”(類名本身或其他名稱)加載的。 通常將ClassLoader.loadClass()與自定義類加載結合使用,通常用于此目的。 OSGi提供了OSGi服務機制,用于注冊和發現這種“命名”的實現,這將使您完全擺脫動態類加載。 該機制是OSGi固有的,它為上述自定義機制提供了一種非常優雅的替代方法。 與上一節中介紹的方法相比,此方法的缺點在于,它需要對代碼進行一些更深層次的更改,尤其是如果它也應繼續在OSGi之外運行的話。
您需要考慮以下方面:
- 在OSGi服務注冊表中注冊您的接口和實現。
- 在運行時在使用它們的代碼中發現這些實現。
盡管您可以通過編程方式注冊服務,但在大多數情況下,您還是希望使用OSGi聲明式服務方法,因為它允許以純聲明性的方式將現有實現注冊為OSGi服務。 關于發現,您可以直接通過BundleContext提供的功能查詢服務注冊表,也可以使用功能更強大的服務跟蹤器機制 。
關于OSGi服務,尤其是聲明式服務,有很多很棒的教程,其中包括:
- OSGi服務– Lars Vogel的教程 。
- OSGi入門: Neil Bartlett的聲明式服務簡介 。
在我們的案例中,我們不想太大地改變我們的代碼庫,因此我們只在少數幾個我們認為積極影響可以證明投資合理的地方使用了OSGi服務。 目前,我們通過添加服務組件XML將現有的實現聲明為服務。 盡管這種基于XML的方法是標準且常用的方法,但我們發現它相當冗長且不便。 另一種方法是使用注釋來指定組件和服務,如聲明性服務Wiki頁面和OSGi Release 4草案文件所述 。 BND已經支持這些注釋。
其他注意事項
所有包裝僅以一捆出口
從多個軟件包中導出同一軟件包在OSGi中效果不佳,因此必須避免。 如果您的代碼中有這種情況,則應適當地重命名這些軟件包。
公開OSGi入口點
最后,您可能需要提供一個新的入口點,以便從OSGi控制臺啟動您的應用程序。 如果使用Equinox,則一種合適的機制是創建Equinox應用程序 ,該應用程序包括實現org.eclipse.equinox.app.IApplication接口并提供一個附加的plugin.xml,如Eclipse插件入門:命令中所述。在線應用程序 。 可以使用startApp命令從Equinox OSGi控制臺啟動此應用程序。
結論
通過遵循本文中介紹的準則和方法,可以使普通的Java應用程序和庫OSGi相對較少的工作量,并且對您現有的代碼具有可管理的影響,從而使其兼容。
您在使Java代碼與OSGi兼容方面有類似的經驗嗎? 如果是的話,我很想聽聽。
參考:通過我們的JCG合作伙伴 Stoyan Rachev的Stoyan Rachev的Blog博客,可以使Plain Old Java OSGi兼容 。
翻譯自: https://www.javacodegeeks.com/2012/11/making-plain-old-java-osgi-compatible.html
osgi java
總結
以上是生活随笔為你收集整理的osgi java_使普通的旧Java OSGi兼容的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用基于微服务的流架构更好地进行大规模的
- 下一篇: 成都购房网签备案查询系统(成都购房网签备