歡迎進入GlassFish OSGi-JavaEE專題!自從GlassFish v3開始,一個新的特性被加入到GlassFish中,那 就是GlassFish OSGi-JavaEE。本專題將分為九個部分向大家介紹GlassFish OSGi-Java EE相關的知識:
Part1:對GlassFish OSGi-JavaEE做簡單的介紹並簡要敘述企業級的OSGi開發的現狀。
Part2: 理解GlassFish OSGi/WEB容器並開發和部署一個WEB 應用程序Bundle到GlassFish中。
Part3:理解 GlassFish OSGi/EJB容器並開發和部署一個EJB 應用程序Bundle到GlassFish中。
Part4:理解 GlassFish OSGi/CDI(上下文依賴注入)規范以及GlassFish OSGi/CDI的實現,同時,展示如何使用GlassFish OSGi/CDI來簡化企業級OSGi的開發。
Part5,Part6:集成EAR和OSGi,其中,Part5會通過集成Apache Aries Application特性來擁抱EAR和OSGi;Part6會通過一個真實的案例來展示如何使用EJB Bundle來橋接EAR 和OSGi以達到相同的目的。
Part7:深入解讀GlassFish、HK2和OSGi三者的關系, 同時,對於社區經常 提到的問題闡述一個自己的觀點。(“HK2在未來將會Dead嗎?”)
Part8:深入理解GlassFish內核(模塊 的配置、加載以及啟動),同時,以一個案例展示如何擴展GlassFish來加入更多自定義模塊。
Part9: 貢獻GlassFish OSGi以及OSGi-JavaEE的流程。
本專題將假定讀者擁有GlassFish和OSGi的基本知識, 限於篇幅原因,我將不會專門介紹GlassFish和OSGi的基本知識。如果讀者剛剛接觸GlassFish,建議大家參考 以下鏈接:
如果讀者並不了解OSGi,推薦大家閱讀以下的書籍:
Neil Bartlett’s “OSGi in Practice”
Richard S. Hall等 “OSGi In Action”
如果用一句話來定義”什麼是 GlassFish OSGi-JavaEE”的話,那麼我們可以這樣說:
GlassFish OSGi-JavaEE就是企業級OSGi的GlassFish實現。
從2005年GlassFish 1.0到現在4.0的發布,GlassFish已經走過了9個年頭,在這9年中,GlassFish經歷了 JavaEE5到JavaEE7的進化,經歷了Sun被Oracle的收購,經歷了架構上的重大變更……在我看來,最為重要的 改變是GlassFish 3.0的內核和設計理念的重大變更,甚至說是一次革命! 3.0之前,GlassFish是一個不可分 割或者說是一個整體化(monolithic)的龐然大物,內核與組件以及各個組件之間緊耦合,缺乏足夠靈活的擴展 性,啟動性能不高。3.0時代,情況發生了根本性的轉變,內核依托HK2和OSGi,各個模塊完全采用OSGi設計理 念,GlassFish變得更加輕量級和模塊化,內核與組件以及各個組件之間實現了松耦合,有著更加良好的可擴 展性,啟動性能有了質的提高,更為重要的是大大提高了可維護性。同時,更多有特點的模塊出現了, GlassFish OSGi-JavaEE就是其中之一。而這一切都要歸功於OSGi!
另一方面, 放眼當前世界上其他主 流的開源JavaEE應用服務器,如JBOSS 7(現在已經更名為Wildfly), Apache Geronimo 3等均采用模塊化的設 計理念(盡管JBOSS 7沒有直接采用OSGi作為內核),力圖使應用服務器瘦身,更加具有可維護性和可擴展性 。
我們已經看到模塊化的設計理念在JavaEE應用服務器領域大獲成功,OSGi作為模塊化設計理念中最 具代表性的產物,功不可沒!
本專題並不專門講述OSGi的基本知識以及如何采用OSGi來設計JavaEE應 用服務器,相反,我們探討如何使用OSGi來開發企業級Java應用。但是,當提到OSGi與企業級Java時,我們勢 必將會拋出一個問題:為什麼企業級Java需要使用OSGi?
為了回答這個問題,我們需要進一步解答以下 的兩個重要的問題:
企業級Java開發有哪些不足?
借助OSGi能夠解決這些不足嗎?
企業級Java開發有哪些不足
企業級Java是由構建在核心Java上的一系列庫、API以及框架松散構成 的,並且這些庫、API以及框架為開發人員提供了一系列的企業服務,例如,分布式組件開發、事務、持久化 訪問數據庫等。企業級Java已經取得了巨大的成功(無論是 JavaEE 本身還是 Spring 框架),但是,隨著企 業級Java應用程序的規模和復雜度的不斷增加,這些應用程序已經開始變得更加得笨重和龐大,標准Java的一 些固有的問題也越發變得嚴重,有時甚至是致命的。
classpath地獄
眾所周之,標准Java的 classpath是扁平(flat)的結構,也就是說,是以線性順序在指定的classpath中搜索類。例如,圖1中,
圖1: 標准Java中扁平(flat)的classpath結構
類A有兩個不同的版本,放在兩個不同的JAR中,對於 某些被部署的應用程序來說,也許它們想要使用版本1的類A,對於另一些被部署的應用程序來說,也許它們希 望使用版本2的類A。但是,扁平的classpath結構決定了很有可能不會加載版本2的類A,因為版本1的類A將首 先被發現和加載。
對於小的程序,也許我們能夠迅速發現這個問題並且通過調整jar包在classpath中 的順序來解決。但是,當應用程序的規模和復雜性日益升級時,這個應用程序的classpath可能非常的大(由幾 十個甚至幾百個JAR構成),搜索classpath花的時間也會很大,一旦丟失了希望加載的某個類的話,很難在短 時間內被發現。以下的一些場景進一步描述了這個問題。
classpath中缺失了運行時的依賴
也 許因為某種原因,當應用程序運行時,當前應用程序的classpath中缺失了某個依賴。那麼,在最好的情形下 ,能夠在運行的早期通過拋出ClassNotFoundException異常發現這樣的缺失。但是,在最糟糕的情形下,運行 的早期不會發現缺失的依賴(因為沒有運行到相關的邏輯),直到數個星期(甚至幾年)之後,通過拋出的 ClassNotFoundExcept異常才能發現這樣的缺失,但是,修補的成本可能會很大,如果你不夠走運的話,這樣 的一個依賴缺失很可能會導致一個已經運行了很長時間的關鍵任務(Mission-Critical)程序(如股票交易,大 型在線購物等)突然崩潰,而你卻束手無策。
classpath中的版本沖突
也許有一種方式能夠回 避“依賴突然缺失”的問題,即包裝(wrap)所有的依賴到這個應用程序之中(例如:對於WAR來說,我們將依賴 放置到到WEB-INF/lib中)。我們暫且不考慮效率問題,因為對於每個應用程序都用到的一些共通的依賴,包裝 一份拷貝是一種資源的冗余,關鍵是這種包裝的方式未必能夠使應用程序正確地運行。想象一下這樣一種場景 :如果運行在同一個JVM的多個應用程序公用某一項依賴的話,會是什麼樣的結果呢?
按照這種包裝的 方式,這個公用的依賴將會被每個應用程序都包裝一份拷貝,當其中一個拷貝首先出現在classpath中並且被 加載時,其他的拷貝將被忽略。那麼,如果這個公用依賴的版本一致,不會有什麼問題。但是,當版本不一致 時,一些應用程序將會運行正常,但是另一些應用程序很可能最終會出現” NoSuchMethodError”,因為它們 所調用的方法根本不存在(它們所希望依賴的版本被其他版本屏蔽了)。在一些更糟糕的情形下,沒有顯式的異 常或錯誤拋出,程序看起來也在正常地運行,但是運行的行為是錯誤的,這並不容易被發現。
讓我們 從JavaEE的視角再看一下這樣的包裝方式,在一個JavaEE環境中,每個實例(例如: GlassFish的Instance)都 可能駐留多個應用程序,為了解決“依賴突然缺失”的問題, JavaEE規范建議包裝每個應用程序的依賴到該 應用程序之中,看起來這是一個理想的解決方案,但是,情況可能更加得糟糕,因為JavaEE應用服務器本身就 可能依賴了很多開源的庫,而應用程序也可能依賴了相同的這些庫。這樣的話,即便所有的應用程序都使用了 相同版本的庫,但因為JavaEE應用服務器使用了不同版本的庫,就很可能導致這些應用程序運行異常,但是, 這樣的問題通常很難發現也很難調試。
復雜讓事情看起來更糟糕
應用程序在日益變得復雜,問 題也在不斷地發生。BBC News新聞站點上的一篇文章[1]提到:
The core of the problem is that the business software used by the institutions has become horrifically complex, according to Lev Lesokhin, strategy chief at New York-based software analysis firm Cast.
He says developers are good at building new functions, but bad at ensuring nothing goes wrong when the new software is added to the existing mix.
“Modern computer systems are so complicated you would need to perform more tests than there are stars in the sky to be 100% sure there were no problems in the system,” he explains.
簡單地說,2012年銀行系統中發現了許多嚴重的問題,這些問題導致了支付處理中的損失和其他問題。這 篇文章預測了當應用程序變得復雜時,這樣的損失將會在未來變得更為普遍。
就復雜性而言,有兩層 的含義:
a. 應用程序的邏輯變得更為復雜
b. 應用程序的結構變得更為復雜,模塊更為龐大
無論是a還是b,這樣的應用程序有很多都是由耦合性很高的代碼構成的,用Holly Cummins和Timothy Ward[2]的話來說,這些都是混亂(spaghetti)的代碼。下圖演示了一個具有混亂代碼構成的應用程 序:
圖2: 一個帶有很少結構化的並且高度相互關聯的混亂的應用程序。實線代表著既是編譯期的依賴也是運行 期的依賴,而虛線僅僅代表運行期的依賴。摘自: “Enterprise OSGi In Action”第一章
從圖2中, 不難發現,對於每個對象,很難准確地發現它的每一個依賴以及傳遞性依賴,更不要說發現這些依賴的版本了 。很可能在某個類中,有很多的依賴(這些依賴包括了顯式的依賴,也包括了許多隱式的依賴),對於小規模的 應用程序來說,你可能很容易地發現這些依賴,但是,對於一個復雜性很高的應用程序來說,代碼很龐大也很 難讀,那麼,想要清晰地識別依賴並不容易。
對於這些具有混亂代碼的工程,當未來試圖替換一個依 賴的版本時,很有可能造成程序運行不正確,更嚴重地,導致程序運行崩潰。另外,當試圖為程序增加一個新 的功能時,因為不能清晰地把握程序的每個部分的依賴關系,只能寄希望於做更多的測試,而這些測試中也許 有一些根本沒有必要,如果你打個盹想少測試一些,那麼就有可能招致新的問題,這也就像BBC News的那篇文 章所反映的那樣。
進一步來說,出現混亂代碼的根本原因其實就是JAR本身缺乏顯式的依賴說明。一個 復雜的企業應用可能由多個JAR構成,或者它本身就會被打包成一個JAR,這些JAR不僅依賴標准Java庫還依賴 了其他的一些JAR。因此,如果要部署這樣的企業應用的話,必須也要部署它依賴的其他JAR。例如:Apache JClouds依賴Google Guice和Google Guava等,如果使用JClouds的應用程序的classpath之中沒有Guice和 Guava的話,那麼這個應用程序將不可能正常工作。
但是,我們如何知道這些依賴和傳遞依賴呢?如果 Apache JClouds有很好的文檔說明,列舉了這些依賴,那麼這不會有什麼問題,當然,如果你是一個Maven專 家的話,通過Apache JClouds的工程Pom文件,你也能識別出這些依賴。但是,許多庫並沒有很好的文檔說明 ,而且你可能也不是Maven專家或者這些庫根本就不是用Maven這些好的依賴構建工具構建的,那麼這些依賴對 你來說就是隱式的,當試圖使用這些庫時,你很可能會遇到ClassNotFoundException。
正如Neil Bartlett在” No Solution for Complexity?”[3]中提到的那樣,我們需要一種方式在大型系統的不同部分 創建”防火牆”,當在受到”防火牆”保護的每個部分的外部增加新功能時,能夠確保這些受到保護的部分不 會被攻擊和破壞。這樣的話,我們就能夠精確地知道系統的任何改變所涉及的范圍,然後僅僅針對這些范圍做 測試,而不是全部。
另一方面,應用程序的邏輯變得更為復雜,今天的企業應用程序通常要支持並發 訪問、遠程訪問、持久化數據、事務操作、組件化和分布式(為了考慮伸縮性和負載均衡等)……毫無疑問, JavaEE已經提供了一系列事實的標准去滿足這些需求,並且獲得了巨大的成功。
但是,正如Holly Cummins和Timothy Ward[2]提到的,我們的企業級應用程序帶有Web前端、持久化組件、事務組件等,並且運 行在多個服務器上,所有的這些組件如何粘合到一起也許並不為團隊中每個成員知曉,他們可能僅僅關注自己 所編寫的那部分。
另外,隨著企業級應用程序劃分成越來越多的系統,它們會運行在不同的服務器上 ,這意味著每個系統運行的classpath、可用的依賴以及應用程序服務器本身的技術實現都可能大相徑庭,對 於這些互相關聯的系統,最好是避免指定它們的依賴來自哪裡以及系統的classpath是如何構造的。否則,對 其中一個系統做改變很可能對其他系統有很大的影響。
因此,JavaEE甚至比標准Java更需要為它的應 用程序模塊化。
借助OSGi修復這些問題
當前,OSGi很好地解決了上述的這些不足。簡單地說, OSGi使用核心Java中的ClassLoader並擴展JAR清單(manifest)來創建一個比核心Java更具有模塊化的系統。
OSGi是一個大的話題,如果要清晰地理解它,需要更多的篇幅去介紹,如果你是第一次接觸OSGi,建議 首先閱讀[4],這是我見到過的最好的OSGi書籍之一。
InfoQ曾經推出過一個系列來討論模塊化和OSGi相 關的知識:模塊化Java簡介、模塊化Java:靜態模塊化、模塊化Java:動態模塊化以及模塊化Java:聲明式模 塊化。為了兼顧一些讀者(也許他們的時間有限),我們僅介紹OSGi的幾個重要概念,想了解更多信息的讀者可 以參考以上提及的文獻和文章。
理解OSGi基本概念
OSGi Bundle和Bundle的版本
OSGi Bundle是由標准JAR以及在JAR清單中加上一些額外的OSGi元數據所構成。
OSGi運行時常常被稱作” OSGi框架”,用來管理OSGi Bundle的生命周期並構建各個Bundle之間的依賴關系。在OSGi運行時的外面, Bundle和標准JAR沒有兩樣,但是,在OSGi運行時之中,情況則完全不同,一個Bundle中的類能夠使用另一個 Bundle中類,但是,這裡有個前提,那就是另一個Bundle要顯式地允許它的類被訪問,否則,OSGi運行時將阻 止對這個Bundle中類的訪問。這個訪問規則非常類似Java語言中的可見性修飾符(public,private,protect以 及包級私有)。
OSGi運行時會通過依賴解析,將每個Bundle關聯起來,形成一個依賴的圖,
圖3: OSGi讓各個模塊之間的依賴更加清晰。
摘自: “Enterprise OSGi In Action”第一章
相比較圖2混亂的結構,從圖3中,我們能夠清晰地識別各個模塊之間的依賴,這一切都要歸功於OSGi 。
另一方面,最重要的一點是通過Bundle的包導入和導出機制,你不必再擔心或猜測在應用程序運行 時會丟失哪些依賴,因為,一旦真的丟失了這些依賴,OSGi運行時會檢測出丟失的依賴,然後拋出異常,根據 異常你能夠准確地分析出丟失了哪些依賴以及這些依賴的版本。
當然,為了完全消除classpath地獄中 的版本沖突,OSGi Bundle需要版本化。使用OSGi能夠讓具有不同版本的庫(在OSGi環境中,我們稱之為模塊或 Bundle)以及導出的包共存,同時,對於依賴不同版本的庫和包的模塊來說,它們能夠選擇最適合的依賴庫。 如果依賴的庫沒有版本化,那麼就沒有辦法知道舊庫和新庫之間的差異,我們會再次陷入classpath地獄。
限於篇幅,關於OSGi Bundle元數據和版本化,更多的內容請讀者看一下[2]和[4]。
OSGi的動 態性和生命周期管理
動態性對於軟件工程並不是新的概念,但是,它是OSGi的基礎和核心。你也許會 說,通過反射、動態代理以及URLClassLoader,也能夠實現動態性,那麼,OSGi為什麼會為動態性建立一個新 的模型呢?
簡單地說,通過以上的方式來實現動態性勢必要利用Java底層的API,而OSGi試圖為開發人 員提供一種更加方便和友好的方式來達到這一目標。
Bundle的生命周期
Bundle不像普通的JAR 那樣安靜地呆在CLASSPATH中,Bundle能夠根據需要啟動和停止,一旦Bundle啟動了,那意味著它的所有導入 依賴都被滿足了。Bundle的啟動和停止過程就像一個狀態機一樣,通過下圖,我們能夠清晰地發現Bundle的生 命周期中的各個階段:
圖4: Bundle的生命周期中的各個階段。
摘自: “Enterprise OSGi In Action”第一章
Bundle能夠在已安裝(Installed)、已解析(Resolved)、正在啟動(Starting)、已啟動(Active)、正在 停止(Stopping)以及已卸載(Uninstalled)這6個狀態中進行遷移。
正在啟動(Starting)和正在停止 (Stopping)更多的是一個暫態,例如,當啟動完成之後,Bundle將進入已啟動狀態。Bundle處在解析狀態的條 件是它已經被安裝,並且它的所有依賴都被解析或者說被滿足。當一個Bundle被卸載時,它不再能夠提供包給 任何新的Bundle。
關於OSGi的其他一些概念如服務與服務注冊表、ClassLoading等,限於篇幅,沒有 辦法一一介紹,詳細的內容也請讀者參考[2]和[4]。
回到我們的問題,也許你會問,既然很多應用服務 器廠商已經采用了OSGi作為它們的內核,是否我們可以簡單地將企業級Java和OSGi相結合?
很不幸, 答案是否定的,因為一些原因,JavaEE編程模型與OSGi並不兼容,關於這一點,Holly Cummins和Timothy Ward[2]給出了清晰的解釋。
為什麼OSGi和JavaEE不能很好地結合?
框架和類加載
典型 的JavaEE應用服務器會駐留多個企業級Java應用程序,為了在這些應用程序之間提供某種層次的隔離,JavaEE 應用服務器會建立一個嚴格的類加載體系,通過這個類加載體系,應用程序之間相互隔離(這裡的隔離指的是 應用程序之間不能互相訪問各自的資源),應用程序與應用服務器之間也相互隔離。這個類加載體系基於標准 Java的ClassLoader體系[6]。以下是一個典型的JavaEE應用服務器的類加載體系,
圖5: 一個典型的JavaEE應用服務器的類加載體系
摘自: Neil Bartlett’s “OSGi in Practice” [7]
如圖5所示,每一個ClassLoader僅僅能夠加載它自己和它祖先定義的類,它不可能加載到同等層次 的其他ClassLoader定義的類。因此,對於一些希望被EJB和WEB共享的類來說,它們必須要放置到更高的層次( 例如, EAR ClassLoader),另外,如果有一些類希望被所有應用程序共享,那麼,它們必須要放置到 Application ClassLoader中,通常,這是通過配置應用服務器本身來達到的。
另一方面,從圖5我們 能夠發現應用程序的類不可能很容易地被應用服務器本身的類加載,這似乎不是什麼大的問題,但是應用服務 器中的一些容器為應用程序提供了插入點或者說”鉤子”,通過回調應用程序的代碼來完成一些共通的邏輯。 因此,應用服務器必須要訪問應用程序的類。
這個問題通過線程上下文ClassLoader(Thread Context ClassLoader)[8]能夠回避。通過正確地設置線程上下文ClassLoader,框架能夠檢索這個ClassLoader,然後 訪問應用程序以及它的模塊中的類。但是,這也意味著,
線程上下文ClassLoader必須在框架檢索它之前在其他地方正確地被設置,但是,在OSGi中,線程上下文 ClassLoader通常並不會被設置。
使用線程上下文ClassLoader完全違反了系統的模塊化,不能保證由線程上下文ClassLoader加載的類會匹 配你的類空間。關於這一點,可以想象一下,如果線程上下文ClassLoader加載了一個類A的舊版本,而你的類 空間希望匹配類A的新版本,那麼,這種不匹配將導致大的問題。
我們提到,在OSGi中,線程上下文ClassLoader常常很少被設置,但是,凡事都不是絕對的,例如, GlassFish OSGi-JavaEE的OSGi/WEB模塊就配置了線程上下文ClassLoader來加載一些應用程序Bundle的類。
關於線程上下文ClassLoader的討論和使用,我建議讀者看一看Neil Bartlett’s “The Dreaded Thread Context Class Loader”[9]以及” OSGi Readiness — Loading Classes”[10],這兩篇文章給出了 一些精辟的觀點。
META-INF 服務(META-INF/services)與ServiceLoader模式
從JDK6開始, 出 現了一種能夠發現給定接口的所有實現的模式,稱之為ServiceLoader[11](注意: 普通的反射無法發現給定接 口的所有實現[12])。
在這種模式下,任何JAR都能夠注冊一個給定接口的實現並且將實現的名稱放置 在這個JAR的META-INF/services目錄下的一個文件中,該文件的名字要根據所實現的接口來命名。然後,通過 ServiceLoader的load方法來獲取classpath中的所有接口的實現。例如,我們有一個接口JAR(AInf.jar),其 中定義了一個接口AInf,
public interface AInf { long getCount(); … }
我們再定義一個AInf的實現JAR(AImpl.jar),其中定義了一個實現類AImpl,
package sample.serviceLoader.internal public Class AImpl implements AInf { … }
然後,Java客戶端通過ServiceLoader獲取AInf的所有實現,
ServiceLoader loader = ServiceLoader.load(AInf.class); AInf service = loader.iterator().next();
這種方式對於標准Java來說已經做得很好了,但是, 正如[13]中提到的那樣,它有一定的局限性,最明顯的是當運行時希望加入新的接口實現時,ServiceLoader 模式就不能發揮作用了,也就是說,ServiceLoader不具有動態的可擴展性。
回到OSGi的話題,當希望 將這樣的代碼移植到OSGi環境中時,問題將變得更多。
ServiceLoader模式利用了線程上下文ClassLoader去發現META-INF/services目錄下的所有的資源。關於線 程上下文ClassLoader,我們在上面已經提到,在OSGi的環境中,通常不會定義線程上下文ClassLoader,所以 有可能造成ServiceLoader模式失效。
OSGi環境下,Bundle之間的聯系是通過” Import-Package”和”Export-Package”實現的,對於一個給定 的導入,只能有一個確定的導出,因此,不可能通過ServiceLoader模式來獲取接口的多個實現。
初始化一個接口的實現一般需要訪問實現類的內部細節,例如,我們的AImpl類定義在*..internal包中, 對於OSGi來說,Bundle一般都不會導出內部的細節(因為這會打破Bundle的封裝性),因此,ServiceLoader模 式也將失效。即便能夠導出內部的細節,將客戶端綁定到具體的實現,這也會違反松耦合的准則。
Bundle有動態的生命周期,意味著實現Bundle很可能悄無聲息的離開OSGi運行時,但是,ServiceLoader模 式並沒有提供一種事件機制來通知客戶端。
JavaEE沒有實現動態的執行環境
由於標准Java的種種不足以及JavaEE應用服務器的類加載體系, JavaEE
並沒有實現動態的執行環境,這意味著當你的WEB應用程序或者EJB模塊被部
署並且執行 時,你不可能再添加新的Servlet/JSP或者更新EJB模塊。應用程序
所能發揮的范圍被綁定在了部署時 。
幸運的是,隨著企業級OSGi的出現,OSGi和JavaEE之間的鴻溝已經變得越來越小了。
企業級 OSGi與JavaEE的集成
2007年6月,OSGi聯盟(OSGi Alliance)發布了OSGi Service Platform Release 4.1,在這個Release中,首次加入了面向企業級OSGi的規范,到2013年,OSGi企業專家組(EEG)已經發布了 OSGi Enterprise Release 5的最終Draft,在這個Release 5中,加入了更多的面向企業OSGi的服務規范。
我們必須要指出,如果企業級OSGi不能提供更多在JavaEE中也可用的服務,那麼企業級OSGi將變得毫 無用處,因為,對於JavaEE來說,已經積累了非常龐大的開發群體,社區已經變得非常成熟,如果企業級OSGi 提供了完全不同於JavaEE的規范或做法,那麼將對JavaEE社區的開發群體毫無幫助,也不可能得到更多人的認 同。
關於這一點,也正是驅動企業級OSGi不斷向前發展的根本所在。
企業級OSGi的規范很多, 覆蓋了許多與JavaEE集成的服務。例如,
WEB應用程序規范
一個OSGi版本的WEB應用程序規范(chapter 128 in the OSGi Enterprise Release 5 Specification[14]) ,這個規范的目的是為了部署一個既存的和新的WEB應用程序到運行在OSGi框架中的Servlet容器裡,而部署的 模型應該類似於JavaEE環境中WEB應用程序的部署。
這個規范定義了“Web Application Bundle(WAB)”, 它是一個Bundle且與JavaEE中WAR執行同樣的角色。 WAB使用了OSGi的生命周期以及OSGi的類/資源加載規則而沒有使用標准JavaEE環境的加載規則。WAB是常規的 Bundle,因此能夠使用OSGi框架中的所有特性。
一個傳統的WAR也能夠作為WAB進行安裝,這是通過清單重寫來完成的。關於這一點,GlassFish 4.0已經實 現了這個特性。
JPA服務規范
Java持久化API是JavaEE中一個重要的規范。JPA提供了一個對象關系映射(ORM)模型,這個模型通過持久化 描述符來配置。企業級OSGi的JPA服務規范(chapter 127 in the OSGi Enterprise Release 5 Specification[14])定義了持久化單元如何能夠被發布到OSGi框架中、客戶端Bundle如何發現這些持久化單元 ,以及通過OSGi JDBC規范如何能夠發現數據庫驅動等。
除了上述兩個服務規范之外,還有許多有用的服務規范,如,Java事務服務規范(chapter 123 in the OSGi Enterprise Release 5 Specification[14])以及Blueprint容器規范(chapter 121 in the OSGi Enterprise Release 5 Specification[14]),後者是企業級OSGi中最重要的規范之一,連同Declarative Services規范(chapter 112 in the OSGi Enterprise Release 5 Specification[14])和RFC 193 CDI集成規 范[15],均被視為OSGi中的依賴注入規范,我認為依賴注入規范是企業級OSGi最吸引人的幾個規范之一。另外 ,Service Loader Mediator規范(chapter 133 in the OSGi Enterprise Release 5 Specification[14])的 引入正是為了解決我們在上面提到的ServiceLoader模式的一些問題。
限於本專題的篇幅,不能一一提 到每個服務規范,感興趣的讀者可以詳細閱讀OSGi Enterprise Release 5 Specification[14]。
接下 來,讓我們看看企業級OSGi在GlassFish中的實現狀況。
GlassFish中的企業級OSGi
首先, 我們 將再次回顧一下GlassFish與OSGi的關系。其次,我們將看一下GlassFish中的企業級OSGi。
GlassFish與OSGi的關系
前面已經提到,自從GlassFish 3.0開始,GlassFish的內核已經完全采用 OSGi,並且內核由一系列OSGi Bundle實現。當啟動Glassfish時,首先啟動OSGi運行時(默認地,OSGi運行時 是Apache Felix),然後,GlassFish通過擴展OSGi框架的配置機制(對於Apache Felix,文檔[5]很好地說明了 框架的配置屬性),安裝並且啟動內核Bundle。由於GlassFish是一個實現了JavaEE規范的應用服務器,因此, 實現JavaEE規范的各個容器或者說組件都被包裝成了OSGi Bundle。那麼,當啟動GlassFish時,這些容器 Bundle也將被安裝到OSGi運行時中。
理解GlassFish OSGi-JavaEE
GlassFish不僅僅實現了最新 的JavaEE規范,而且也暴露JavaEE組件模型和API給OSGi應用程序Bundle,換句話說,OSGi開發人員現在也能 夠使用JavaEE組件模型(例如: JSF、JSP、EJB、CDI、JPA、JTA等)。這正是企業級OSGi所希望達成的:與 JavaEE模型相集成。這對於從事企業級OSGi開發人員來說是非常重要的,因為他們現在既能夠使用JavaEE的強 大而成熟的特性,又能夠達到OSGi模塊化以及面向服務所帶來的好處。
在GlassFish中,JavaEE平台的 服務(例如: 事務服務、HTTP服務、JMS服務等)都被視為OSGi服務,因此,OSGi應用程序Bundle能夠通過OSGi 服務注冊表去獲取這些服務。
因此,我們給GlassFish OSGi-JavaEE做一個准確的定義:
“GlassFish開啟了OSGi和JavaEE的雙向交互。一方面,由OSGi框架管理的OSGi服務能夠激活由JavaEE容器管 理的JavaEE組件。另一方面,由JavaEE容器管理的JavaEE組件能夠激活由OSGi框架管理的OSGi服務。”
應用程序開發人員能夠聲明性地導出EJB作為OSGi服務,不必寫任何OSGi服務導出代碼。這樣就允許任 何純的OSGi組件(沒有運行在JavaEE上下文中)去發現這個EJB,然後去激活它的業務方法等。類似的,JavaEE 組件能夠定位由非JavaEE OSGi Bundle提供的OSGi服務,然後使用這些服務。另外,我們在未來專題中將看到 ,GlassFish擴展了上下文依賴注入(CDI)使得JavaEE組件以類型安全的方式使用動態的OSGi服務更加得便利。 以下是GlassFish中OSGi-JavaEE相關的各個模塊的位置關系。
圖6: GlassFish中OSGi-JavaEE相關的容器的位置關系
摘自: OSGi Application Development using GlassFish Server[16]
在GlassFish OSGi-JavaEE中,基於OSGi Bundle所使用的特性,它們被 劃分成兩類,
① 普通(plain vanilla)的OSGi Bundle
這些Bundle不包含任何JavaEE組件也不 使用任何JavaEE特性。
② 使用JavaEE的OSGi Bundle(也稱作合成應用程序Bundle)
這些Bundle 包含JavaEE組件。因此,這樣的Bundle不僅僅是一個OSGi Bundle,而且是一個JavaEE制品(artifact)。
對於使用GlassFish OSGi-JavaEE開發的應用程序來說,它們能夠使用如下兩類API,
OSGi API
目前,GlassFish 4.0帶有一個符合OSGi R4.2的框架,因此所有OSGi 4.2的核心API都可用,進一步地, GlassFish也帶有許多一般性的OSGi服務,例如,
OSGi配置Admin服務
OSGi事件Admin服務
OSGi Declarative服務
GlassFish使用了來自Apache Felix的這些服務的實現。
JavaEE API
一個用戶部署的OSGi Bundle不僅僅能夠使用OSGi API,而且也能夠使用JavaEE API。值得注意的一點是, GlassFish 4是一個實現了JavaEE 7的應用服務器,因此,用戶能夠使用最新的JavaEE API。但是,並非所有 JavaEE API都對各種類型的OSGi Bundle可用,
類別A: 由JavaEE容器管理的組件,例如,EJB、CDI、Servlet、JSF以及JAX-RS等。這些組件由容器管理, 因此,它們對於普通(Plain vanilla)的OSGi Bundle不可用,因為普通的OSGi Bundle不受JavaEE運行時管理 。一個OSGi Bundle必須成為合成應用程序Bundle才能夠使用這些組件相關的API。
類別B: 訪問平台服務的API,例如,JNDI、 JTA、 JDBC以及JMS等,OSGi Bundle的開發人員也能夠使用這 些API。典型地,一個Bundle必須使用JNDI去訪問服務和資源,但GlassFish實際上使這些平台服務變成了OSGi 服務,因此,OSGi應用程序開發人員能夠使用OSGi服務API去訪問這些平台服務。
類別C: 功能API,像JAXB、JAXP以及JPA等。
絕大多數這些API都有一個插入層從而允許應用程序插入不同的實現,典型地,這種可插入性是通過標准 Java的ServiceLoader模式來完成的,我們前面提到過,ServiceLoader模式是利用了線程上下文ClassLoader 來發現接口的所有實現,因此,必須要設置正確的線程上下文ClassLoader。但是,GlassFish沒有這樣的限制 ,對於部署在GlassFish的OSGi Bundle來說,可以安全地使用這些功能API。
在接下來的各個專題中,我們將詳細地理解GlassFish OSGi-JavaEE的各個模塊以及演示如何開發合成應用 程序Bundle來更好地結合JavaEE和OSGi。
在Part2中我們將首先看一下GlassFish OSGi/WEB模塊,一方 面帶領大家深入理解OSGi WEB規范,另一方面學習如何部署一個WEB 應用程序Bundle到GlassFish中。