以下案例研究旨在使OSGi捆綁包和服務的上述優勢具體化。 它采用了一個有趣的Java項目vert.x,并展示了如何將其嵌入OSGi并利用OSGi的功能。
免責聲明:我不建議更換vert.x容器或其模塊系統。 這主要是在OSGi的使用中進行的案例研究,盡管某些發現應該促使對vert.x進行改進,尤其是將其嵌入具有自定義類加載器的應用程序中時。
版本號
vert.x開源項目提供了node.js的JVM替代方案:異步,事件驅動的編程模型,用于以多種語言(包括Java,Groovy,JavaScript和Ruby)編寫Web應用程序。
vert.x支持HTTP以及現代協議,例如WebSockets和sockjs (與WebSockets 相比 ,它們在更多的瀏覽器中工作,并且可以更輕松地穿越防火墻)。
vert.x具有分布式事件總線,允許已知為verticles并稱為busmods共享代碼庫vert.x應用程序之間進行傳播JSON消息。 busmod是一種特殊的Verticle,它處理事件總線中的事件。 vert.x艦艇一些busmods,如MongoDB的 “persistor”,用戶可以寫自己的。

vert.x的線程模型很有趣,因為每個頂點(或busmod)在其生命周期內都綁定到特定線程,因此,頂點代碼無需關注線程安全性。 線程池用于在頂點上分派工作,并且每個頂點必須避免阻塞或長時間運行的操作,以免影響服務器吞吐量(vert.x提供了有效地實現長時間運行的操作的單獨機制)。 這類似于CICS事務處理器中的準可重入線程模型。 1個
這里特別受關注的是vert.x模塊系統,該系統在每個頂點上都有一個類加載器和稱為模塊的代碼庫,它們被加載到使用它們的每個頂點的類加載器中。 因此,除了通過事件總線之外,沒有其他方法可以在各個頂點之間共享代碼。
vert.x具有出色的文檔,包括主要手冊 , java手冊 (以及其他語言的手冊), 教程和可運行的代碼示例 。
OSGi
如果您還不熟悉OSGi,請閱讀我的OSGi簡介文章,但現在不要再理會該文章中的鏈接-您可以隨時返回并稍后再做。
將vert.x嵌入OSGi
我通過幾個小步驟完成了此操作,下面依次介紹了這些步驟:將vert.x JAR轉換為OSGi捆綁包,然后模塊化verticle,busmod和事件總線客戶端。
將vert.x JAR轉換為OSGi捆綁軟件
vert.x手冊鼓勵用戶使用vert.x核心JAR將vert.x嵌入其自己的應用程序中,因此將vert.x嵌入OSGi的第一步是將vert.x核心JAR轉換為OSGi捆綁包,因此可以將其加載到OSGi運行時中。
我使用了Bundlor工具,盡管其他工具(例如bnd)也可以很好地工作。 Bundlor接受一個模板,然后分析JAR的字節碼以生成帶有適當OSGi清單標頭的新JAR。 請立即參閱SpringSource Bundlor文檔以獲取有關Bundlor的更多信息,因為在撰寫本文時Eclipse Virgo Bundlor文檔尚未發布,即使Bundlor項目已轉移到Eclipse.org。
vert.x核心JAR的模板如下:
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.vertx.core
Bundle-Version: 1.0.0.final
Bundle-Name: vert.x Core
Import-Template:org.jboss.netty.*;version="[3.4.2.Final,4.0)",org.codehaus.jackson.*;version="[1.9.4,2.0)",com.hazelcast.*;version="[2.0.2,3.0)";resolution:=optional,groovy.*;resolution:=optional;version=0,org.codehaus.groovy.*;resolution:=optional;version=0,javax.net.ssl;resolution:=optional;version=0,org.apache.log4j;resolution:=optional;version=0,org.slf4j;resolution:=optional;version=0
Export-Template: *;version="1.0.0.final"
(此案例研究的模板和所有其他部分可在github上找到 。)
這是為JAR所依賴的軟件包定義有效的版本范圍(范圍“ 0”表示0或更大的版本范圍),這些軟件包是可選的還是強制的,以及JAR自己的軟件包應為哪個版本出口處。 它還為捆綁軟件提供了符號名稱 (用于標識捆綁軟件),版本和(描述性)名稱。 有了這些信息,OSGi然后通過委派包類加載器之間的類加載和資源查找,將包的依賴關系連接在一起。
值得慶幸的網狀網絡JAR和杰克遜 JSON JAR文件將vert.x核心JAR取決于附帶有效的OSGi清單。
為了驗證清單是否有效,我嘗試在處女座內核中部署vert.x核心軟件包。 只需將vert.x核心捆綁包放置在Pickup目錄中,并將其依賴項放置在repository / usr目錄中,然后啟動內核即可。 以下控制臺消息顯示vert.x核心捆綁包已安裝并成功解決:
<hd0001i> Hot deployer processing 'INITIAL' event for file 'vert.x-core-1.0.0.final.jar'.
<de0000i> Installing bundle 'org.vertx.core' version '1.0.0.final'.
<de0001i> Installed bundle 'org.vertx.core' version '1.0.0.final'.
<de0004i> Starting bundle 'org.vertx.core' version '1.0.0.final'.
<de0005i> Started bundle 'org.vertx.core' version '1.0.0.final'.
然后使用處女座外殼,檢查線束的接線:
osgi> ss
"Framework is launched."id State Bundle
0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106
...
89 ACTIVE org.vertx.core_1.0.0.final
90 ACTIVE jackson-core-asl_1.9.4
91 ACTIVE jackson-mapper-asl_1.9.4
92 ACTIVE org.jboss.netty_3.4.2.Finalosgi> bundle 89
org.vertx.core_1.0.0.final [89]...Exported packages...org.vertx.java.core; version="1.0.0.final"[exported]org.vertx.java.core.buffer; version="1.0.0.final"[exported]...Imported packagesorg.jboss.netty.util; version="3.4.2.Final"<org.jboss.netty_3.4.2.final [92]>...org.codehaus.jackson.map; version="1.9.4"<jackson-mapper-asl_1.9.4 [91]>...
我還按照以后需要的類似方式將vert.x平臺JAR轉換為OSGi捆綁軟件。
模塊化頂點
一個典型的頂點如下所示:
public class ServerExample extends Verticle {public void start() {vertx.createHttpServer().requestHandler(new Handler<httpserverrequest>() {public void handle(HttpServerRequest req) {...}}).listen(8080);}
}
調用start方法時,它將創建一個HTTP服務器,并向該服務器注冊一個處理程序,并設置服務器在端口上偵聽。 除了處理程序的主體之外,該代碼的其余部分都是樣板。 因此,我決定將樣板分解為一個通用的OSGi捆綁包(org.vertx.osgi),并用包含處理程序和一些等同于樣板的聲明性元數據的模塊化頂包替換該頂標。 常見的OSGi捆綁包使用白板模式來偵聽OSGi服務注冊表中的特定種類的服務,基于元數據創建樣板,并向生成的HTTP服務器注冊處理程序。
讓我們看一下模塊化的vertical bundle。 它的代碼包含一個HttpServerRequestHandler類: 2
public final class HttpServerRequestHandler implements Handler<httpserverrequest> {public void handle(HttpServerRequest req) {...}}
它還具有服務屬性形式的聲明性元數據,該聲明性元數據與OSGi服務注冊表中的處理程序一起注冊。 我可以使用OSGi藍圖服務來執行此操作,盡管我可以使用OSGi聲明性服務,甚至可以使用OSGi API以編程方式注冊該服務。 藍圖元數據是捆綁軟件中的文件blueprint.xml ,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"><service interface="org.vertx.java.core.Handler" ref="handler"><service-properties><entry key="type" value="HttpServerRequestHandler"><entry key="port" value="8090"></service-properties></service><bean class="org.vertx.osgi.sample.basic.HttpServerRequestHandler"id="handler"/></blueprint>
此元數據聲明應創建HTTP服務器(通過類型服務屬性),向其注冊的處理程序以及偵聽端口8090的服務器集(通過端口服務屬性)。 當org.vertx.osgi捆綁包運行時,這一切都是通過白板模式完成的,如下所示。
請注意,模塊化Verticle僅依賴于Handler和HttpServerRequest類,而原始Verticle也依賴于Vertx,HttpServer和Verticle類。 對于那些喜歡單元測試(除了容器內測試)的人來說,這也使事情變得簡單得多,因為所需的模擬或存根數量更少。
那么我們現在有什么呢? 將兩個包添加到我們先前安裝的包中:一個org.vertx.osgi包,它封裝了樣板代碼;一個應用程序包,它代表一個模塊化的verticle。 我們還需要一個Blueprint服務實現-從Virgo 3.5開始,Virgo內核內置了一個Blueprint實現。 以下交互圖顯示了一種可能的事件序列:

在OSGi中,每個捆綁軟件都有其自己的生命周期,并且通常設計捆綁軟件時,無論它們相對于其他捆綁軟件啟動的順序如何,它們都將正確運行。 在上面的示例中,假定的開始順序為:藍圖服務,org.vertx.osgi包,模塊化verticle包。 但是,org.vertx.osgi捆綁包可以在模塊化Verticle捆綁包之后開始,并且最終結果將是相同的:將創建服務器,并且在服務器上注冊模塊化Verticle捆綁包的處理程序,并且服務器設置監聽。 如果藍圖服務是在org.vertx.osgi和模塊化Verticle捆綁包之后啟動的,那么直到藍圖服務啟動后,org.vertx.osgi捆綁包才會檢測到模塊化注冊表包的處理程序服務出現在服務注冊表中,但是最終結果將再次相同。
github項目包含一些示例模塊verticle的源代碼: 基本的HTTP垂直版本 (在8090端口上運行)和sockjs verticle (在8091端口上運行)。 org.vertx.osgi捆綁軟件需要更多的代碼來支持sockjs,而模塊化的sockjs verticle除了提供HTTP處理程序外,還需要提供sockjs處理程序。
模塊化BusMods
MongoDB持久程序是處理事件總線消息的busmod的典型示例:
public class MongoPersistor extends BusModBase implements Handler<message<jsonobject>> {private String address;private String host;private int port;private String dbName;private Mongo mongo;private DB db;public void start() {super.start();address = getOptionalStringConfig("address", "vertx.mongopersistor");host = getOptionalStringConfig("host", "localhost");port = getOptionalIntConfig("port", 27017);dbName = getOptionalStringConfig("db_name", "default_db");try {mongo = new Mongo(host, port);db = mongo.getDB(dbName);eb.registerHandler(address, this);} catch (UnknownHostException e) {logger.error("Failed to connect to mongo server", e);}}public void stop() {mongo.close();}public void handle(Message<jsonobject> message) {...}}
再次,混合了樣板代碼(用于注冊事件總線處理程序),啟動/停止邏輯,配置處理以及事件總線處理程序本身。 我對其他豎版應用了類似的方法,并將樣板代碼分離到org.vertx.osgi包中,將處理程序和元數據(包括配置)保留在模塊化busmod中。 持久性對MongoDB客戶端JAR(mongo.jar)的依賴很方便,因為此JAR附帶了有效的OSGi清單。
這是blueprint.xml :
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"><service ref="handler" interface="org.vertx.java.core.Handler"><service-properties><entry key="type" value="EventBusHandler"/><entry key="address" value="vertx.mongopersistor"/></service-properties></service><bean id="handler" class="org.vertx.osgi.mod.mongo.MongoPersistor"destroy-method="stop"><argument type="java.lang.String"><value>localhost</value></argument><argument type="int"><value>27017</value></argument><argument type="java.lang.String"><value>default_db</value></argument></bean></blueprint>
請注意,樣板配置由處理程序類型和事件總線地址組成。 其他配置(主機,端口和數據庫名稱)特定于MongoDB持久程序。
這是模塊化的MongoDB busmod代碼 :
public class MongoPersistor extends BusModBaseimplements Handler<Message<JsonObject>> {private final String host;private final int port;private final String dbName;private final Mongo mongo;private final DB db;public MongoPersistor(String host, int port, String dbName)throws UnknownHostException, MongoException {this.host = host;this.port = port;this.dbName = dbName;this.mongo = new Mongo(host, port);this.db = this.mongo.getDB(dbName);}public void stop() {mongo.close();}public void handle(Message<JsonObject> message) {...}}
該代碼仍然擴展了BusModBase,僅僅是因為BusModBase提供了幾種方便的輔助方法。 同樣,與非模塊化等效代碼相比,生成的代碼更簡單,更易于單元測試。
模塊化事件總線客戶端
最后,我需要一個模塊化的Verticle來測試模塊化的MongoDB持久性。 這些verticle需要做的就是將適當的消息發布到事件總線。 普通的vert.x垂直版本使用Vertx類獲取事件總線,但是我再次使用了Blueprint服務,這一次是在服務注冊表中查找事件總線服務,并將其注入到模塊化垂直版本中。 我還擴展了org.vertx.osgi捆綁包,以便在服務注冊表中發布事件總線服務。
模塊化事件總線客戶端的blueprint.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"><reference id="eventBus" interface="org.vertx.java.core.eventbus.EventBus"/><bean class="org.vertx.osgi.sample.mongo.MongoClient"><argument ref="eventBus"/><argument type="java.lang.String"><value>vertx.mongopersistor</value></argument></bean></blueprint>
然后, 模塊化事件總線客戶端代碼非常簡單:
public final class MongoClient {public MongoClient(EventBus eventBus, String address) {JsonObject msg = ...eventBus.send(address, msg,new Handler<Message<JsonObject>>(){...});}}
旋轉一下
1.我已經在git的bundles目錄中提供了所有必需的OSGi捆綁包 。 您可以通過克隆git存儲庫來獲取它們:
git clone git://github.com/glyn/vert.x.osgi.git
或通過下載git repo的zip文件 。
2. vert.x需要Java 7 ,因此請設置終端外殼以使用Java7 。確保正確設置了JAVA_HOME環境變量。 (如果現在無法獲取Java 7,則將捆綁軟件部署到OSGi時會看到一些錯誤,并且您將無法在步驟8和9中運行示例。)
3.如果您是OSGi用戶,只需在您喜歡的OSGi框架或容器中安裝并啟動捆綁軟件,然后跳至步驟8。否則,請按以下方式使用git存儲庫中的Virgo內核副本。
4.將目錄更改為git repo本地副本中的virgo-kernel-…目錄。
5.在UNIX上,發出:
bin/startup.sh -clean
或在Windows上,發出:
bin\startup.bat -clean
6.處女座內核應在其拾取目錄中啟動并部署各種捆綁軟件:
- org.vertx.osgi捆綁包(
org.vertx.osgi-0.0.1.jar
) - HTTP示例模塊化
org.vertx.osgi.sample.basic-1.0.0.jar
(org.vertx.osgi.sample.basic-1.0.0.jar
) - SockJS示例模塊化verticle(
org.vertx.osgi.sample.sockjs-1.0.0.jar
) - MongoDB持久性示例模塊化busmod(
org.vertx.osgi.mods.mongo-1.0.0.jar
)
7.如果要查看現在正在運行的捆綁軟件,請從另一個終端啟動Virgo Shell:
telnet localhost 2501
并使用ss
或lb
命令匯總已安裝的捆綁軟件。 help
命令將列出其他可用命令,而disconnect
將使您脫離Virgo Shell。 這是ss
命令的典型輸出:
...
89 ACTIVE org.vertx.osgi_0.0.1
90 ACTIVE jackson-core-asl_1.9.4
91 ACTIVE jackson-mapper-asl_1.9.4
92 ACTIVE org.jboss.netty_3.4.2.Final
93 ACTIVE org.vertx.core_1.0.0.final
94 ACTIVE org.vertx.osgi.mods.mongo_1.0.0
95 ACTIVE com.mongodb_2.7.2
96 ACTIVE org.vertx.platform_1.0.0.final
97 ACTIVE org.vertx.osgi.sample.basic_1.0.0
98 ACTIVE org.vertx.osgi.sample.sockjs_1.0.0
和lb
命令(包括更具描述性的Bundle-Name標頭):
...89|Active | 4|vert.x OSGi Integration (0.0.1)90|Active | 4|Jackson JSON processor (1.9.4)91|Active | 4|Data mapper for Jackson JSON processor (1.9.4)92|Active | 4|The Netty Project (3.4.2.Final)93|Active | 4|vert.x Core (1.0.0.final)94|Active | 4|MongoDB BusMod (1.0.0)95|Active | 4|MongoDB (2.7.2)96|Active | 4|vert.x Platform (1.0.0.final)97|Active | 4|Sample Basic HTTP Verticle (1.0.0)98|Active | 4|Sample SockJS Verticle (1.0.0)
8.現在,您可以使用Web瀏覽器在localhost:8090嘗試基本的HTTP示例,該示例應響應“ hello”,或在http:// localhost:8091的SockJS示例應顯示一個框,您可以在其中鍵入一些文本和一個按鈕,單擊該按鈕會彈出一個窗口:

9.如果要嘗試(無頭的)MongoDB事件總線客戶端,請下載MondoDB并在其默認端口上本地啟動 ,然后將org.vertx.osgi.sample.mongo-1.0.0.jar
從bundles
目錄復制到Virgo的提取目錄。 此捆綁包啟動后,它將立即向事件總線發送一條消息,并驅動MongoDB持久程序更新數據庫。 如果您不想使用MongoDB來檢查是否進行了更新,請查看處女座的日志(在serviceability/logs/log.log
)以查看一些System.out行,例如以下內容,該行確認發生了某些情況:
System.out Sending message: {action=save, document={x=y}, collection=vertx.osgi}
...
System.out Message sent
...
System.out Message response {_id=95..., status=ok}
OSGi和vert.x模塊化
在本案例研究中,各種示例OSGi捆綁包都依賴于并共享vert.x核心捆綁包。 每個捆綁包都加載在其自己的類加載器中,并且OSGi根據OSGi捆綁包的連接方式控制類加載和資源查找的委派。 以相同的方式,寫為OSGi包的頂點可以自由地依賴和共享其他OSGi包。
這與vert.x模塊系統大不相同,在vert.x模塊系統中,一個verticle依賴的任何模塊(除了busmod之外)都被加載到與verticle相同的類加載器中。
OSGi模塊系統的優點是,每個模塊的單個副本安裝在系統中,并且對于諸如Virgo shell之類的工具可見并且可以由其管理。 它還使占地面積最小。
vert.x模塊系統的優點是,在各個頂點之間不存在模塊共享,因此,編寫不當的模塊不會無意或有意地泄漏獨立頂點之間的信息。 此外,每個使用它的垂直模塊都有每個(非busmod)模塊的單獨副本,因此可以編寫模塊而不必擔心線程安全,因為每個副本僅在其垂直線程上執行。 但是,OSGi用戶可能很高興要求可重用的模塊具有線程安全性,并謹慎地管理任何可變的靜態數據,以避免線程之間的泄漏。 ?
更換容器?
當我提出將vert.x嵌入OSGi的話題時, vert.x的負責人蒂姆·福克斯(Tim Fox)問我是否正在編寫當前容器的替代品,對此我回答“不是真的”。 我之所以這樣說,是因為我喜歡vert.x的事件驅動編程模型及其線程模型,它們似乎是“容器”的一部分。 但我想更換一對夫婦的vert.x容器方面:模塊系統和verticles登記處理的方式。
后來讓我吃驚的是,在模塊化系統中,“容器”作為整體實體的概念可能有點奇怪,最好考慮多個單獨的容器概念,然后以不同的方式組合以適應不同的容器用戶。 但是,上面看到的類加載和線程模型之間的微妙交互表明,包含的不同概念可以相互依賴。 我想知道其他人如何看待“容器”的概念嗎?
結論
由于OSGi框架是一個相當嚴格的應用程序,因此vert.x聲稱它可以嵌入其他應用程序中的說法已得到驗證。
vert.x模塊系統盡管未在模塊之間提供隔離,但確實在應用程序之間(包括垂直模塊及其模塊)提供了隔離,并且使模塊的編寫無需關注線程安全。
提出了一個vert.x問題2 ,這應該使vert.x易于使用自定義類加載器嵌入其他環境。
vert.x可以遵循netty,jackson和MongoDB JAR的示例,并在其核心JAR和平臺JAR中包含OSGi清單,以避免OSGi用戶不得不將這些JAR轉換為OSGi捆綁包。 我將這個問題留給其他人提出,因為我無法評估在OSGi中使用vert.x的需求。
在OSGi中運行vert.x可以滿足一些出色的vert.x要求,例如如何自動化容器內測試(OSGi有許多解決方案,包括Pax Exam,而Virgo有集成測試框架)以及如何開發verticles并將它們部署到vert .x在IDE的控制下(請參閱Virgo IDE工具指南 )。 處女座還提供了許多附帶的好處,包括用于檢查和管理束和頂點的管理外殼,復雜的診斷程序以及更多其他功能(有關詳細信息,請參閱處女座白皮書 )。
該練習還為處女座帶來了一些不錯的收益。 修復了370253錯誤 ,這是在Java 7下運行Virgo的唯一已知問題。Virgo3.5依賴于在此環境中中斷的Gemini藍圖,因此引發并修復了錯誤379384 。 我使用了新的基于Eclipse的Virgo工具來開發各種捆綁軟件并在Virgo中運行它們。 結果,我在工具中發現了一些小問題,這些問題將適時解決。
最后,在Virgo內核上運行vert.x是對內核是否適合構建自定義服務器運行時的進一步驗證,因為現在除了Tomcat,Jetty以及在內核上運行的一兩個自定義服務器之外,我們還具有vert.x。
腳注:
- 在IBM的日子里,我曾在CICS開發團隊中工作。 SpringSource的一位同事給了我“ CICS做到了!” 我們開始合作后不久就穿了T恤。 舊習難改。
- 模塊化垂直模塊當前需要攔截vert.x的資源查找邏輯,以便可以輕松提供捆綁中的文件。 將此通用代碼移至org.vertx.osgi捆綁包會更好,但這需要首先實現vert.x問題161 。
參考: OSGi案例研究:來自Mind the Gap博客的JCG合作伙伴 Glyn Normington 的模塊化vert.x。
翻譯自: https://www.javacodegeeks.com/2012/07/osgi-case-study-modular-vertx.html