• 
    

    
    

      99热精品在线国产_美女午夜性视频免费_国产精品国产高清国产av_av欧美777_自拍偷自拍亚洲精品老妇_亚洲熟女精品中文字幕_www日本黄色视频网_国产精品野战在线观看 ?

      Java重定義接口實現(xiàn)代碼的自動注入

      2013-09-18 10:30:58嚴忠林
      微型電腦應(yīng)用 2013年8期
      關(guān)鍵詞:代碼定義成員

      嚴忠林

      0 引言

      接口是 Java語言中重要的程序組件,許多廣泛使用的應(yīng)用框架,比如spring等,都是基于接口工作的。設(shè)計者利用接口可以擺脫具體實現(xiàn)的羈絆,從問題處理的本身需求出發(fā),定義對象間通訊的方法。它可以超越類繼承方面的限制,給實現(xiàn)代碼保留最大的自由度,使整個系統(tǒng)低耦合、易擴展、更靈活通用。所以幾乎每本介紹 Java編程的書都會建議程序員“基于接口編程”。

      但接口機制也有一明顯的缺陷。它必須由實現(xiàn)類來提供各方法的執(zhí)行代碼。因此一旦某接口有了較多數(shù)量的實現(xiàn)類,再要修改該接口就困難了。一個典型的例子是 JDK1.0中定義的 Enumeration接口,有 hasMoreElements和nextElement兩個方法。當JDK1.2希望增加remove操作時,由于不能影響已實現(xiàn)類,只得又重新定義一個類同的新接口Iterator。這導致像Vector這樣的相關(guān)類,不得不再添加一套類似的方法,它無疑提高了代碼的復雜性和開發(fā)工作量。Java核心類庫如此,用戶自定義的接口同樣也會遇到。在軟件的整個生命周期中,用戶需求、運行環(huán)境常發(fā)生變化,會要求已定義的接口也隨之改變,比如需要添加新方法、修改參數(shù)列表等。這會使先前已完成的類不作修改就無法使用。假如這樣的類數(shù)量不多,程序員依靠人工還能應(yīng)付。但當它們數(shù)量較多,或者沒有源代碼時,就比較難解決了,有時會因此妨礙系統(tǒng)的優(yōu)化升級。

      這些由新情況、新要求帶來的改變,一般不會導致已完成類的內(nèi)部工作邏輯發(fā)生變化。多數(shù)情況可用相同的模式統(tǒng)一處理它們。比如對于為新需求而添加的方法,已有的類大都可用相同的代碼完成處理。有時這些新方法在已有類中從不會被調(diào)用,因此只要提供不執(zhí)行任何操作的空方法就行了。相比于用手工在源碼層次上一個一個類重復地進行代碼修改、編譯、保存等操作,更好的方案是將這些對每個已完成類都相同的,需要添加、修改的元素,制作成統(tǒng)一的bytecode模板,再將它直接注入到相關(guān)類的類代碼中,使這些類立刻滿足新接口的要求。這樣,無論有多少已完成類,都可以在極短時間內(nèi)實現(xiàn)更新。

      能夠直接在bytecode層次上完成這一工作,還繞過了對源代碼的需求。即使應(yīng)用系統(tǒng)使用了來自開發(fā)團隊外部的代碼,也照樣能進行必要的修正。

      這種對整個系統(tǒng)的修復優(yōu)化工作,一般都要經(jīng)過若干次更新、測試、再更新、再測試的反復處理。為使這一過程簡單高效,由虛擬機在運行時自動完成上述的代碼注入是最方便的。這樣,程序員僅需修改接口,準備好需要的模板,原有各實現(xiàn)類的類文件保持原樣,就可啟動系統(tǒng),調(diào)試運行了。相關(guān)類代碼的變更是在它被裝載時自動完成的。不需要繁瑣的操作,程序員就能獲得期望的結(jié)果。

      1 標注被更新的接口

      為了聲明哪些接口已作了修改,指示虛擬機進行處理,采用在對應(yīng)接口前添加標注的辦法,同時也用它指定應(yīng)用的模板,如圖1所示:

      圖1 實現(xiàn)注入的標注的定義及示例

      定義了 ImplementInjection標注。虛擬機將檢查所有實現(xiàn)這些接口的類,對其中缺少的或需替換的方法執(zhí)行注入。

      通常一個接口的所有實現(xiàn)類可使用同一模板,但有時也會遇到需要分門別類,給不同類型的實現(xiàn)類提供不同模板的情況,所以又使用 Implement數(shù)組來提供這些信息。Implement是為此定義的另一標注,它有兩個屬性,都是用字符串表示的類全名,implement指定作為注入模板的類文件,type給出使用該模板的類文件的類型信息,沒有指定適用類型的模板將用于所有其他類。虛擬機將按照它們在數(shù)組中出現(xiàn)的次序?qū)Ω飨嚓P(guān)類進行類型匹配。

      圖1中給出的示例對接口Abc進行了標注,它聲明對類CA及其子類以ImpA、對實現(xiàn)IB接口的所有類以ImpB、其他實現(xiàn)類以ImpC作為模板進行注入。標注使用時也可以不提供任何模板,注入時將默認地用不執(zhí)行任何操作但能滿足語法要求的空方法來代替。

      2 提供注入模板

      對一個接口的修改,可能有以下幾種情況。

      刪除某方法,對于已完成類,這不用作任何修改。

      添加新方法,需要為已完成類注入新的實現(xiàn)代碼。

      添加或修改已有方法的參數(shù)列表,對于已完成類,這相當于添加新方法。

      修改已有方法的返回值的類型,需要修改原實現(xiàn)代碼。

      修改已有方法拋出的異常列表,如果僅是增加異常類型,對已完成類沒有影響。否則要修改原代碼。

      也存在表面上未作修改,但對具體實現(xiàn)提出新要求的情況,這也需要用新的實現(xiàn)代碼替換已完成類中的舊代碼。

      模板中包含將在已完成的各實現(xiàn)類中添加或修改的方法代碼,應(yīng)由程序員根據(jù)實際情況編寫。從語法上看,它是一個普通的Java類,編譯后會形成標準格式的*.class文件。但實質(zhì)上它僅是一個存放注入時所需代碼的容器。虛擬機處理某個實現(xiàn)類時,將比對相關(guān)的接口及模板,判定需要添加或替換的元素,然后執(zhí)行注入,使之成為該實現(xiàn)類的一部分。因此這些代碼的真正運行環(huán)境是被注入的實現(xiàn)類,它可以直接訪問該類里面原有的任何成員。

      作為模板的java類中可能包含下列成員。

      接口中新添加的方法,發(fā)現(xiàn)實現(xiàn)類中缺少該方法時將注入。

      實現(xiàn)代碼需要替換的方法,它們要加上額外的標注,指示處理時進行替換。

      以上方法的代碼所引用的其他數(shù)據(jù)和方法,它們又可分為:

      原實現(xiàn)類中已有的成員,新添加的方法如果需要,當然可以使用它們。它們并不需要注入,出現(xiàn)在模板中是為滿足模板編譯時的語法要求。因此,只要類型聲明和原有類一致,沒有實際代碼的空方法、未初始化的變量等,都可滿足要求。注入處理時將直接跳過。

      新添加的成員,它們是由于新注入代碼運行的需要而引入的新元素,應(yīng)該和這些代碼一起注入。為區(qū)別于其他成員,也加以標注,以確保處理時能正確注入。

      圖1中也出現(xiàn)了這些標注的定義,Hold用于標注那些被注入代碼所需要的新元素,只要某個類文件需要代碼注入,它們就一定隨之一起注入。即使原有類中已有了同名的元素,它們?nèi)詫⒃诟拿蟊蛔⑷?。當然,引用它們的代碼也要作相應(yīng)改變。

      Replace用于標注那些需要用新代碼替換類文件中舊代碼的方法。有些情況下,該方法原有的處理邏輯仍然有效,新代碼只不過是在其基礎(chǔ)上添加一些額外處理,如進行類型轉(zhuǎn)換或與新環(huán)境的交互等。此時對舊代碼不能一刪了之,而應(yīng)修改名字后在新的實現(xiàn)代碼中繼續(xù)使用。標注中的 value屬性就用于說明在此情況下,新代碼中調(diào)用原方法所使用的名字,注入時要作適當處理。

      3 成員注入的實現(xiàn)

      Java編譯后獲得的類文件和其他語言的可執(zhí)行文件不同,源程序結(jié)構(gòu)和幾乎所有的標識符信息依然保留其中,代碼中對各變量、方法的使用都通過符號引用實現(xiàn)。正是由于這些特性,才使得代碼注入簡便可行。

      SUN公司為類文件定義了規(guī)范的結(jié)構(gòu),理解并遵守這一規(guī)范[1],是正確實現(xiàn)注入的基礎(chǔ)。其構(gòu)成,如圖2所示:

      圖2 Java類文件結(jié)構(gòu)

      其中常量池包含了類代碼中用到的所有常量、名字和類型信息。類自身、父類及其實現(xiàn)的接口通過常量池索引連接起來,代碼中各數(shù)據(jù)訪問、方法調(diào)用也通過常量池中由名字、類型信息等組成的符號引用實現(xiàn)。類中出現(xiàn)的變量、方法及各種標注等屬性都有規(guī)則地羅列,其內(nèi)部信息也有明確的規(guī)范。因此,要判定是否需要注入,應(yīng)該注入什么元素,只要查看類文件即可。注入操作也只是將需要的數(shù)據(jù)、方法成員從模板中取出,復制插入到類文件的合適位置中去。稍微復雜的是常量池的修改,需要添加或改變其中的元素。

      直接解析類文件執(zhí)行以上操作還是比較復雜的,但現(xiàn)在已有專門處理類文件的工具ASM。ASM是一個小型高效、使用方便的開源軟件,可以在 Java虛擬機的“匯編語言”bytecode層上完成類文件的解析、生成、變換。它提供的基于事件的API[3],快速簡潔,非常適合這里需要的注入操作。它隱藏了常量池等底層處理,提供了幾個采用訪問者模式,用戶可方便實現(xiàn)所需功能的基本組件。其中ClassReader能解析已存在的類文件,對其中的不同元素,會調(diào)用用戶提供的處理類的相應(yīng)visit方法。ClassWriter提供能直接生成類文件中各元素二進制表示的方法,用戶按合適次序調(diào)用,就能生成正確的類文件。ClassVisitor、FieldVisitor、MethodVisitor、AnnotationVisitor等為訪問類文件中各元素定義了 visit方法,用戶編寫子類,用自己的處理邏輯覆蓋相應(yīng)方法,就可以實現(xiàn)各種希望的功能。只要將這些類按需連接成處理鏈,就能對類文件各元素進行獲取、插入、刪除、變換等操作[4]。

      代碼注入將類文件分成3種作不同處理。

      定義接口的類文件:要檢查標注,確定是否對其實現(xiàn)類進行注入操作。需要的話記錄其中各方法,以及模板文件等信息。

      定義模板的類文件:要記錄其中各成員的相關(guān)信息。

      定義普通類的類文件:首先要查看其是否實現(xiàn)了需要注入的接口。如否,不作任何處理。否則,選定應(yīng)使用的模板。并查看內(nèi)部已有成員及父類中可訪問成員,明確需要注入或替換的元素。然后將模板中相應(yīng)成員插入類文件中,同時完成改名等操作。對接口中要求注入而模板中未提供代碼的方法,自動生成不執(zhí)行任何操作,返回符合類型要求的0或null的空方法代替。

      4 注入操作的自動執(zhí)行

      這種對類文件的變換處理,可以作為軟件開發(fā)中一個單獨的步驟進行,但當牽涉的文件量較多,或需要多次反復修改調(diào)試的場合,更方便的做法是讓虛擬機自動發(fā)現(xiàn)需要注入的類,在使用前自動完成變換操作。

      Java類是運行時按需動態(tài)裝載的。虛擬機中有多個類裝載器,負責獲取不同來源的類文件,有些應(yīng)用還會使用自定義的類裝載器。每個類僅在程序首次運行至需要它時,才由某個類裝載器進行裝載。類文件從裝載到可以使用要經(jīng)過載入、連接、初始化等步驟[1],其中連接又包含代碼合法性驗證、存儲空間獲取和可選的引用類解析等操作,它們的執(zhí)行還會遞歸地引發(fā)其父類、相關(guān)接口、引用類的類裝載。

      因此,要讓虛擬機自動實現(xiàn)注入操作,就應(yīng)該在類代碼載入后連接前完成必要的判定、修改工作,用它替換原代碼執(zhí)行后續(xù)的裝載流程。JDK中的Java.lang.instrument包可實現(xiàn)此功能[2]。它定義了Instrumentation和ClassFileTransformer兩個接口。Instrumentation對象由虛擬機提供,用于設(shè)置和啟動對類文件的檢查、變換。ClassFileTransformer定義了transform方法,它由用戶實現(xiàn),具體完成對類代碼的處理。該方法會在類裝載器裝載了新類,對其進行合法性驗證之前執(zhí)行,也會在 instrumentation對象對已裝載類發(fā)出retransformClasses請求后執(zhí)行。使用這個包時,用戶還要提供一個包含premain方法的類,在其中用instrumentation對象注冊用戶的 transform方法。系統(tǒng)啟動時用適當?shù)拿钚羞x項,使其在應(yīng)用程序的main方法之前執(zhí)行。這樣就可以使程序的所有類文件在完成了期望的變換后才被執(zhí)行。

      在實現(xiàn)代碼注入時,只要在 transform方法中引入對應(yīng)處理即可。由于在進行注入時要參照所處理類的實現(xiàn)接口、父類以及插入模板,因此需要改變虛擬機載入它們的次序。對于尚未載入的父類、接口等,不能等到合法性驗證時才裝載,而需主動先行載入。對于先前已載入但未記錄相應(yīng)信息的父類,可通過instrumentation發(fā)出retransformClasses請求,重新得到它的類代碼,獲取需要的信息。這樣,用戶在修改了接口,并提供了模板文件后,就可運行原程序,所有的變換處理會在運行前自動完成。

      在premain方法中還加入開關(guān)選項,指定類代碼的存儲位置,使變換后的類代碼除了直接被虛擬機使用外,也存儲起來。在所有的開發(fā)測試完成后,可以能將它們直接部署到生產(chǎn)環(huán)境中去。

      5 總結(jié)

      通過直接在類文件中注入接口需要的實現(xiàn)方法,較好地改變了已有大量實現(xiàn)類的接口不便于修改的境況。這有助于改善對已完成的大型軟件進行升級優(yōu)化的工作效率,也有助于提高已有軟件的可復用性。實際上,這種做法適用于大量類都有相同方法代碼的場合??杀苊庖荒R粯拥奈谋静坏貌怀霈F(xiàn)在多個地方的窘境。在僅支持單繼承的 Java語言中,它對于系統(tǒng)的開發(fā)和維護是有益的。再進一步,這種做法還初步達成了接口的實現(xiàn)代碼和其他代碼的分離。對同一個接口,可以提供不同的具體實現(xiàn),替換簡單容易。這對于要為同一軟件,根據(jù)不同運行環(huán)境,生成不同的子版本是有幫助的。

      [1]Tim Lindholm, Frank Yellin. The Java Virtual Machine Specification Second Edition [M]Addison-Wesley Professional 1999.4

      [2]Oracle co. Java Platform, Standard Edition 7 API Specification[EB/OL].http://docs.oracle.com/javase/7/docs/api/

      [3]Eric Bruneton. ASM 4.0 A Java bytecode engineering library[EB/OL].http://download.forge.objectweb.org/asm/asm4-guide.pdf

      [4]Eugene Kuleshov. Using the ASM framework to implement common Java bytecode transformation patterns[J].http://asm.ow2.org/current/asm-transformations.pdf

      猜你喜歡
      代碼定義成員
      主編及編委會成員簡介
      主編及編委會成員簡介
      主編及編委會成員簡介
      主編及編委會成員簡介
      創(chuàng)世代碼
      動漫星空(2018年11期)2018-10-26 02:24:02
      創(chuàng)世代碼
      動漫星空(2018年2期)2018-10-26 02:11:00
      創(chuàng)世代碼
      動漫星空(2018年9期)2018-10-26 01:16:48
      創(chuàng)世代碼
      動漫星空(2018年5期)2018-10-26 01:15:02
      成功的定義
      山東青年(2016年1期)2016-02-28 14:25:25
      修辭學的重大定義
      當代修辭學(2014年3期)2014-01-21 02:30:44
      兴化市| 哈巴河县| 武宁县| 沛县| 措勤县| 东乡族自治县| 祁门县| 兴文县| 尖扎县| 辽宁省| 佳木斯市| 枣庄市| 扶沟县| 马龙县| 潼关县| 晋宁县| 八宿县| 石嘴山市| 徐水县| 唐海县| 土默特右旗| 应城市| 正阳县| 平阳县| 阿图什市| 清苑县| 广宁县| 修水县| 滨州市| 澄迈县| 阿坝| 池州市| 安庆市| 行唐县| 吐鲁番市| 彩票| 炎陵县| 西乌珠穆沁旗| 徐汇区| 个旧市| 开原市|