類加載器特技:OSGi代碼生成_第1頁
類加載器特技:OSGi代碼生成_第2頁
類加載器特技:OSGi代碼生成_第3頁
類加載器特技:OSGi代碼生成_第4頁
類加載器特技:OSGi代碼生成_第5頁
已閱讀5頁,還剩7頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領

文檔簡介

1、類加載器特技:OSGi代碼生成把大型系統(tǒng)移植到OSGi架構上常常意味著解決 復雜的類加載問題。這篇文章專門 研究了面向這個領域最難問題的幾個框架:有關動態(tài)代碼生成的框架。這些框架也 都是些超酷的框架:AOP包裝器,ORM映射器以及服務代理生成器,這些僅僅是一 些例子。我們將按照復雜性增加的順序考察一些類加載的典型問題,開發(fā)一小段代碼來解 決這些問題中最有趣的一個。即使你不打算 馬上寫一個代碼生成框架,這篇文章也 會讓你對靜態(tài)定義依賴的模塊運行時(如OSGi系統(tǒng))的低級操作有比較深入的了 解。這篇文章還包括一個可以工作的演示 項目,該項目不僅包含這里演示的代碼,還有 兩個基于ASM的代碼生成器可

2、供實踐。類加載地點轉換把一個框架移植到OSGi系統(tǒng)通常需要把框架按照extender模式重構。這個模式允 許框架把所有的類加載工作委托給OSGi框架,與此同時保留對應用代碼的生命周 期的控制。轉換的目標是使用應用bundle的類加載來代替?zhèn)鹘y(tǒng)的繁復的類加載策 略。例如我們希望這樣替換代碼:ClassLoader appLoader = Thread.curre ntThread().getC on textClassLoader();Class appClass = appLoader.loadClass(com.acme.devices.Si nisterE ngi ne);ClassLoa

3、der appLoader =.Class appClass = appLoader.loadClass(com.acme.devices.Si nisterE ngi ne);替換為:Bun dle appB un dle =.Class appClass = appB un dle.lo adClass(com.acme.devices.Si nisterE ngin e);盡管我們必須做大量的工作以便OSGi為我們加載應用代碼,我們至少有一種優(yōu)美 而正確的方式來完成我 們的工作,而且會比以前工作得更好! 現(xiàn)在用戶可以通過 向OSGi容器安裝/卸載bundle而增加/刪除應用。用戶還可以把

4、他們的應用分解為 多個bun die,在應用之間共享庫并利用模塊化的其他能力。由于上下文類加載器是目前框架加載應用代碼的標準方式,我們在此對其多說兩 句。當前OSGi沒有定義設置上下文類加載器的策略。當一個框架依賴于上下文類 加載器時,開發(fā)者需要預先知道這點,在每次調(diào)用進入那個框架時手工設置上下文 類加載器。由于這樣做易于出錯而其不方便,所以在OSGi下幾乎不使用上下文 類 加載器。在定義OSGi容器如何自動管理上下文類加載器方面,目前有些人正在進 行嘗試。但在一個官方的標準出現(xiàn)之前,最好把類加載轉移到一個具體的應用 bun dleo適配器類加載器有時候我們轉換的代碼有外部化的類加載策略。這意

5、味著框架的類和方法接收明 確的ClassLoader參數(shù),允許我們來決定他們從哪里加載應用代碼。在這種情況下, 把系統(tǒng)轉換到OSGi就僅僅是讓Bundle對象適配ClassLoader API的問題。這是一 個經(jīng)典的適配器模式的應用。public class Bun dleClassLoader exte nds ClassLoader private final Bun dle delegate;public Bun dleClassLoader(B un dle delegate) this.delegate = delegate;Overridepublic Class loadClas

6、s(String name) throws ClassNotFoundException retur n delegateo adClass( name);現(xiàn)在我們可以把這個適配器傳給轉換的框架代碼。隨著新bundle的增減,我們還可 以增加bundle跟蹤代碼來創(chuàng)建新的適配器 一一例如,我們可以“在外部”把一個 Java框架適配到OSGi,避免瀏覽該框架的代碼庫以及變換每個單獨的類加載場所。 下面是將一個框架 轉換到使用OSGi類加載的示意性的例子:Bun dle app =.Bun dleClassLoader appLoader = new Bun dleClassLoader(app)

7、;DeviceSimulati onF ramework simfw =.simfw.simulate(com.acme.devices.Si nisterE ngi ne, appLoader);橋接類加載器許多有趣的Java框架的客戶端代碼在運行時做了很多花哨的 類處理工作。其目的 通常是在應用的類空間中構造本不存在的類。讓我們把這些生成的類稱作增強enhancement)。通常,強類實現(xiàn)了一些應用可見的接口或者繼承自一個應用可 見的類。有時,一些附加的接口及其實現(xiàn)也可以被混入。增強類擴充了應用代碼一應用可以直接調(diào)用生成的對象。例如,一個傳遞給應用 代碼的服務代理對象就是這種增強類對象,它

8、使得應用代碼不必去跟蹤一個 動態(tài) 服務。簡單的說,增加了一些AOP特征的包裝器被作 為原始對象的替代品傳遞給 應用代碼。增強類的生命始于字節(jié)數(shù)組byte,由你喜愛的類工程庫ASM, BCEL,CGLIB)產(chǎn) 生。一旦我們生成了我們的類,必須把這些原始的字節(jié)轉換為一個Class對象,換 言之,我們必須讓某個類加載器對我們的字節(jié)調(diào)用它的defineClass()方法。我們 有三個獨立的問題要解決:類空間完整性-首先我們必須決定可以定義我們增強類的類空間。該類空 間必須“看到”彫多的類以便讓增強類能夠被完全鏈接。*可見性-ClassLoader.defineClass()是一個受保護的方法。我們必須

9、找到一個好方法來調(diào)用它。類空間一致性-增強類從框架和應用bundle混入類,這種加載類的方式對 于OSGi容器來說是“不可見的”。作為結果,增強類可能被暴露給相同類的 不茲容的版本。類空間完整性 增強類的背后支持代碼對于生成它們的Java框架來說是未公開的-這意味著該 框架應該會把該新類加入到其類空間。另一方面,增強類實現(xiàn)的接口或者擴展的類 在應用的類空間是可見,這意味著我們應該在這里定義增強類。我們不能同時在兩 個類空間定義一個類,所以我們有個麻煩。因為沒有類空間能夠看到所有需要的類,我們別無選擇,只能創(chuàng)建一個新的類空間 一個類空間等同于一個類加載器實例,所以我們的第一個工作就是在所有的 應

10、用bun die之上維持一個專有的類加載器。這些叫做橋接類加載器,因為他們通過鏈接 加載器合并了兩個 類空間:pubiic ciass BridgeCiassLoader extends CiassLoader private finai CiassLoader secondary;pubiic BridgeCiassLoader(CiassLoader primary, CiassLoader secondary) super(primary);Overrideprotected Ciass findCiass(String name) throws CiassNotFoundExcepti

11、on return secondary.ioadCiass(name);現(xiàn)在我們可以使用前面 開發(fā)的 BundieCiassLoader:/* Appiication space */Bundie app = .CiassLoader appSpace = new BundieCiassLoader(app);/* Framework space* We assume this code is executed inside the framework*/ CiassLoader fwSpace = this.getCiass().getCiassLoader();/* Bridge */Ci

12、assLoader bridge = new BridgeCiassLoader(appSpace, fwSpace);這個加載器首先從應用空間為請求提供服 務 - 如果失敗,它將嘗試框架空間。請 注意我們?nèi)匀蛔孫SGi為我們做很多重量工作。當我們委托給任何一個類空間時, 我們實際上委托給了一個基于OSGi的類加載器-本質(zhì)上primary和seco ndary 加載器可以根據(jù)他們各自bun die的導入/導出(mport/export )元數(shù)據(jù)將加載工作委 托給其他bun die加載器。此刻我們也許會對自己滿意。然而,真相是苦澀的,合并的框架和應用類空間也許 并不夠!這一切的關鍵是JVM鏈接類

13、(也叫解析類)的特殊方式。對于JVM鏈接類 的工作有很多解釋:簡短的回答:JVM以一種細粒度(一次一個符號)的方式來做解析工作的。冗長的回答:當JVM鏈接一個類時,它不需要被連接類的所有引用類的完整的描述。 它只需要被鏈接類真正使用的個別的方法、字段和類型的信息。我們直覺上認為對 于JVM來說,其全部是一個類名稱,加上一個超類,加上一套實現(xiàn)的接口,加上一 套方法簽名,加上一套字段簽名。所有這些符號是被獨立且延 遲解析的。例如,要 鏈接一個方法調(diào)用,調(diào)用者的類空間只需要給類對象提供目標類和方法簽名中用 到的所有類型。目標類中的其他許多定義是不需要的,調(diào)用者的類加載器永遠不會 收到加載它們(那些不

14、需要的定義)的青求。正式的答案:類空間SpaceA的類A必須被類空間SpaceB的相同類對象所代表,當 且僅當:* SpaceB存在一個類B,在它的符號表(也叫做常量池)中引用著A。* OSGi容器已經(jīng)將SpaceA作為類A的提供者(provider)提供合SpaceB。該聯(lián) 系是建立在容器所有bundle的靜態(tài)元數(shù)據(jù)之上的。例如:假設我們有一個bundle BndA導出一個類A。類A有3個方法,分布于3個接 口中:* IX.methodX(Stri ng)* IY.methodY(Stri ng)* IZ.methodZ(Stri ng)還假設我們有一個bundle BndB,其有一個類B。

15、類B中有一個引用 A a = 和一個方法調(diào)用a.methodY(Hello!)。為了能夠解析類B,我們需要為BndB的類空間引 入類A和類String。這就夠了!我們不需要導入IX或者IZ。我們甚至不需要導入IY, 因為類B沒有用IY -它只用了 A。在另一方面,bundle BndA導出時會解析類A, 必須提供IX,IY,IZ,因為他們作為被實現(xiàn)的接口直接被引用。最終,BndA也不需要 提供IX, IY, IZ的任何父接口,因為他們也沒有被直接引用。現(xiàn)在假設我們希望給類空間BndB的類B呈現(xiàn)類空間BndA的類A的一個增強版本。 該增強類需要繼承類A并覆蓋它的一些或全部方法。因此,該增強類需要

16、看到在所 有覆蓋的方法簽名中使用的類。然而,BndB僅當調(diào)用了所有被覆蓋的方法 時才會 導入所有這些類。BndB恰好調(diào)用了我們的增強覆蓋的所有的A的方法的可能性非 常小。因此,BndB很可能在他的類空間中不會看到足 夠的類來定義增強類。實際上 完整的類集合只有BndA能夠提供。我們有麻煩了!結果是我們必須橋接的不是框架和 應用空間,而是框架空間和增強類的空間 - 所 以,我們必須把策略從“每個應用空間一個橋”變?yōu)椤懊總€增強類空間一個橋”。我們 需要從應用空間到一些第三方bundle的類空間做過渡跳接,在那里,應用導入其想 讓我們增強的類。但是我們?nèi)绾巫鲞^渡跳接呢?很 簡單!如我們所知,每個類對

17、象 可以告訴我們它第一次被定義的類空間是什么。例如,我們要得到A的類加載器, 所需要做的就是 調(diào)用A.class.getClassLoader()。在很多情況下我們沒有一個類對象, 只有類的名字,那么我們?nèi)绾螐囊婚_始就得到A.class?也很簡單!我們可以讓應 用bundle給我們它所看到的名稱“ A”寸應的類對象。然后我們就可以橋接那個類的 空間與框架的空 間。這是很關鍵的一步,因為我們需要增強類和原始類在應用內(nèi)是 可以互換的。在類A可能的許多版本中,我們需要挑選被應用所使用的那個 類的類 空間。下面是框架如何保持 類加載器橋緩存的示意性例子:/* Ask the app to resolv

18、e the target class */Bundle app = .Class target = app.loadClass(com.acme.devices.SinisterEngine);/* Get the defining classloader of the target */ClassLoader targetSpace = target.getClassLoader();/* Get the bridge for the class space of the target */ BridgeClassLoaderCache cache = .ClassLoader bridge

19、 = cache.resolveBridge(targetSpace);橋緩存看起來會是 這樣 :public class BridgeClassLoaderCache private final ClassLoader primary;private final MapClassLoader, WeakReference cache;public BridgeClassLoaderCache(ClassLoader primary) this.primary = primary;this.cache = new WeakHashMapvCIassLoader,WeakRefere nce()

20、;public synchroni zed ClassLoader resolveBridge(ClassLoadersec on dary) ClassLoader bridge = nu II;WeakRefere nce ref = cache.get(sec on dary);if (ref != n ull) bridge = ref.get();if (bridge = n ull) bridge = new BridgeClassLoader(primary, sec on dary);cache.put(sec on dary, new WeakRefere nce(bridg

21、e);retur n bridge;為了防止保留類加載器帶來的內(nèi)存泄露,我們必須使用弱鍵和弱值。目標是不在內(nèi) 存中保持一個已卸 載的bun dle的類空間。我們必須使用弱值,因為每個映射項目的 值BridgeClassLoader)都雖引用著鍵ClassLoader),于是以此方式否定它的“弱 點”。這是WeakHashMap javadoc規(guī)定的標準建議。通過使用一個弱 緩存我們避免 了跟蹤所有的bun die,而且不必對他們的生命周期做出反應??梢娦院玫?,我們終于有了自己的外來的 橋接類空間。現(xiàn)在我們?nèi)绾卧谄渲卸x我們的增 強類?如前所述 問題,defineClass()是BridgeCl

22、assLoader的一個受保護的方法。我 們可以用一個公有方法來覆蓋它,但 這是粗野的做法。如果做覆蓋,我們還需要自 己編碼來檢查所請求的增強類是否已經(jīng)被定義。更好的辦法是遵循類加載器設計 的意圖。該設計告訴我們應該覆蓋findClass(),當findClass()認為它可以由任意二 進 制源提供所請求類時會調(diào)用defineClass()方法。在findClass()中我們只依賴所請求 的類的名稱來做決定。所以我 們的BridgeClassLoade必須自己拿主意:這是一個對“A$Enhaneed類的請求,所以我必須調(diào)用一個叫做A的類的增強類 生成器!然后我在生成的字 節(jié)數(shù)組上調(diào)用defin

23、eClass()方法。然后我返回一個新 的類對象。這段話中有兩個值得注意的地方。.我們?yōu)樵鰪婎惖拿Q引入了一個文本協(xié)議-我們可以給我們的類加載器 傳入數(shù)據(jù)的單獨一項-所請求的類的名稱的字符串。同時我們需要傳入數(shù) 據(jù)中的兩項-原始類的名稱和一個標志,將其(原始類)標志為增強類的主 語。我們將這兩項打包為一個字符串,形式為目標類的名稱$Enhanced。 現(xiàn)在fin dClass()可以尋找增強類的標志$En ha need,如果存在,則提取出目標 類的名稱。這樣我們引入了我們增強類的命名約定。無論何時,當我們在堆 棧中看到一個類名以$En ha need結尾,我們知道這是一個動態(tài)生成的類。為 了

24、減少與正常類名稱沖突的風險,我們將增強類標志做得盡可能特殊(例 女口: $_service proxy)增強是按需生成的-我們永遠不會把一個增強類生成兩次。我們繼承的 loadClass()方法首先會調(diào)用findLoadedClass(),如果失敗會調(diào)用 parent.loadClass(),只有失敗的時候它才會調(diào)用findClass()。由于我們?yōu)槊?稱用了一個嚴格的協(xié)議,保證findLoadedClass(在第二次請求相同類的增強 類時候不會失敗。這與橋接類加載器緩存相結合,我們得到了一個非常有效 的方案,我們不會橋接同樣的bundle空間兩次,或者生產(chǎn)冗余的增強類。這里我們必須強調(diào)通過反

25、射調(diào)用defineClass()的選項。eglib使用這種方法。當我們 希望用戶給我們傳遞一個可用的類加載器時這是一種可行的方案。通過使用反射 我們避免了在類加載器之上創(chuàng)建另一個類加載器的需要,只要調(diào)用它的 defi neClass()方法即可。類空間一致性到了最后,我們所做的是使用OSGi的模塊層合并兩個不同的、未關聯(lián)的類空間。 我們還引入了在這些空間中一種搜索順序,其與邪惡的Java類路徑搜索順序相似 實際上,我們破壞了 OSGi容器的類空間一致性。這里是糟糕的事情 發(fā)生的一個場 景:1. 框架使用包eom.aeme.deviees,需要的是1.0版本。2. 應用使用包 com.acme.

26、devices ,需要的是 2.0 版本。3. 類 A 直接 飲用 com.acme.devices.SinisterDevice。4. 類A$Enhanced在他自己的實現(xiàn)中使用了 com.acme.devices.SinisterDevice 。5. 因為我們搜索應用空間,首先A$Enhanced會被鏈接到com.acme.devices.SinisterDevice 2.0版,而他的內(nèi)部代 碼是基于com.acme.devices.SinisterDevice 1.0編譯的。結果應用將會看到 詭異的 LinkageErrors 或者 ClassCastExceptions 。不用說,這

27、是個問題。唉,自動處理這個問題的方式還不存在。我們必須簡單的確保增 強類的內(nèi)部代碼直 接引用的是“非常私有的”類實現(xiàn),不會被其他類使用。我們甚至可以 為任何我們可 能希望使用的外部API定義私有的適配器,然后在增 強類代碼中引用這些適配器。 一旦我們有了一個良好定 義的實現(xiàn)子空間,我們可以用這個知識來限制類泄露。現(xiàn) 在我們僅僅向框架空間委托特殊的私有 實現(xiàn)類的請求。這還會限定搜索 順序問題, 使得應用優(yōu)先搜索還是框架優(yōu)先搜索對結果沒有影響。 讓所有的事情都可控的一 個好策略是有一個 專有的包來包含所有增 強類實現(xiàn)代碼。那么橋接加載器就可以 檢查以那個包 開頭的類的名稱并將它 們委托給框架加 載

28、器做加 載。最終,我們有時 候可以對特定的單實例Singleton)包放寬這個隔離策略,例如 org.osgi.framework - 我們可以安全的直接基于 org.osgi.framework 編譯我們 的增強類代碼,因為在運行時所有在OSGi容器中的代碼都會看到相同的 org.osgi.framework -這是由OSGi核心保證的。把事情放到一起 所有關于這個類加載的傳說可以被濃縮為下面的 100行代碼: public class Enhancer private final ClassLoader privateSpace;private final Namer namer;priv

29、ate final Generator generator;private final MapClassLoader , WeakReference cache;public Enhancer(ClassLoader privateSpace, Namer namer, Generator generator) this.privateSpace = privateSpace;r = namer;this.generator = generator; this.cache = new WeakHashMapClassLoader ,WeakReference();Suppre

30、ssWarnings(unchecked)public Class enhance(Class target) throws ClassNotFoundException ClassLoader context = resolveBridge(target.getClassLoader(); String name = namer.map(target.getName(); return (Class) context.loadClass(name);private synchronized ClassLoader resolveBridge(ClassLoader targetSpace)

31、ClassLoader bridge = null;WeakReference ref = cache.get(targetSpace); if (ref != null) bridge = ref.get();if (bridge = null) bridge = makeBridge(targetSpace);cache.put(appSpace, new WeakReference(bridge); return bridge;private ClassLoader makeBridge(ClassLoader targetSpace) /* Use the target space a

32、s a parent to be searched first */ return new ClassLoader(targetSpace) Overrideprotected Class findClass(String name) throws ClassNotFoundException /* Is this used privately by the enhancements? */if (generator.isInternal(name) return privateSpace.loadClass(name);/* Is this a request for enhancement

33、? */String unpacked = namer.unmap(name);if (unpacked != null) byte raw = generator.generate(unpacked, name, this); return defineClass(name, raw, 0, raw.length);/* Ask someone else */throw new ClassNotFoundException(name);public interface Namer /* Map a target class name to an enhancement class name. */ String map(String targetClassName);/* Try to extract a target class name or return null. */String unmap(String className);public interface Generator /* Test if this is a private implementation class. */

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論