文章詳情頁
為你的應用程序添加動態(tài)Java代碼
瀏覽:73日期:2024-06-05 16:43:14
內(nèi)容: 編寫可以響應運行時變化的代碼摘要你曾經(jīng)希望你的java代碼能夠像JSP一樣是動態(tài)的嗎?它可以在運行時被修改和重新編譯,同時你的應用程序自動更新。本文闡述了如何讓你的代碼動態(tài)化。同樣的,你的一些源代碼將會被直接部署,而不是編譯好的字節(jié)碼。這些源代碼的任何改變都將引起這些源代碼的再編譯和類的重新裝載。然后你的應用程序就會運行在新的類上,用戶將立即看到這種改變。本文不僅講述了運行時源碼編輯和類裝載,而且還提出一個將動態(tài)代碼與其調(diào)用者分離的設(shè)計方案。調(diào)用者保存對動態(tài)代碼的一個靜態(tài)引用,而不管動態(tài)代碼運行時如何再次裝載,調(diào)用者總能訪問最新的類且不用更新引用。這樣,動態(tài)代碼改變對客戶是透明的。JSP是一種比servlets更有彈性的技術(shù),因為它可以響應運行時的動態(tài)改變。你可以想象一個普通的java類也有這種動態(tài)的能力嗎?如果你能修改服務的執(zhí)行而不用重新部署和更新應用程序,將會是很有趣的。文章說明了如何編寫動態(tài)的代碼。它討論運行時源碼編輯,類的再裝載,和讓動態(tài)類的修改對它的調(diào)用者透明的代理設(shè)計模式。版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時請務必保留以下作者信息和鏈接作者:Li Yang;Amydeng原文:http://www.javaworld.com/javaworld/jw-06-2006/jw-0612-dynamic.htmlMatrix:http://www.matrix.org.cn/resource/article/44/44615_Java+Dynamic+Code.html關(guān)鍵字:Java;動態(tài)代碼一個動態(tài)java代碼的例子讓我們以一個動態(tài)java代碼的例子開始來闡釋真正的動態(tài)代碼意味著什么,為下文的討論做鋪墊。請在源碼中找到這個例子完整的源代碼。這個例子是一個簡單的依靠名叫Postman的服務的java應用程序。Postman服務是一個java接口,僅包括一個方法,deliverMessage():public interface Postman { void deliverMessage(String msg);}這項服務的簡單執(zhí)行是向控制臺打印消息。執(zhí)行類是動態(tài)的代碼。這個類,PostmanImpl,僅是一個普通的 java類,如果不是展開它的源碼代替它的已編譯好的二進制碼:public class PostmanImpl implements Postman { private PrintStream output; public PostmanImpl() { output = System.out; } public void deliverMessage(String msg) { output.println(' Postman ' + msg); output.flush(); }}使用Postman服務的應用程序如下。在main()方法里,循環(huán)從控制行讀取消息并通過Postman服務進行傳遞:public class PostmanApp { public static void main(String[] args) throws Exception { BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in)); // Obtain a Postman instance Postman postman = getPostman(); while (true) { System.out.print('Enter a message: '); String msg = sysin.readLine(); postman.deliverMessage(msg); } } private static Postman getPostman() { // Omit for now, will come back later }}執(zhí)行這個應用程序,輸入一些信息,你將看到控制臺輸出如下(你可以下載該例子并自行運行): DynaCode Init class sample.PostmanImplEnter a message: hello world Postman hello worldEnter a message: what a nice day! Postman what a nice day!Enter a message: 現(xiàn)在讓我們來看看動態(tài)的東西。 不要停止應用程序,讓我們修改PostmanImpl的源碼。新的執(zhí)行程序?qū)阉械男畔⑤敵龅揭粋€文本文件,而不是控制臺。// MODIFIED VERSIONpublic class PostmanImpl implements Postman { private PrintStream output; // Start of modification public PostmanImpl() throws IOException { output = new PrintStream(new FileOutputStream('msg.txt')); } // End of modification public void deliverMessage(String msg) { output.println(' Postman ' + msg); output.flush(); }}回到應用程序,輸入更多信息,將會發(fā)生什么呢?是的,信息都到文本文件里去了。看控制臺: DynaCode Init class sample.PostmanImplEnter a message: hello world Postman hello worldEnter a message: what a nice day! Postman what a nice day!Enter a message: I wanna go to the text file. DynaCode Init class sample.PostmanImplEnter a message: me too!Enter a message: 注意 DynaCode 初始類sample.PostmanImpl再次出現(xiàn)了,說明類PostmanImpl被再次編譯和裝載了。如果你檢查文本文件msg.txt(在working目錄下),你會看到如下內(nèi)容: Postman I wanna go to the text file. Postman me too!令人驚訝,對吧?我們可以在運行時更新Postman服務,這種改變對應用程序是完全透明的。(注意應用程序使用了相同的Postman實例來訪問各種執(zhí)行程序的版本。)實現(xiàn)動態(tài)代碼的四個步驟讓我來揭示在這種表象后面到底發(fā)生了什么。基本上,構(gòu)成動態(tài)代碼分四個步驟:+部署選擇的部分源代碼并監(jiān)控源代碼文件的變化+在運行時編譯java代碼+在運行時裝載/重裝載java類+將最新的類鏈接給它的調(diào)用者部署選擇的部分源代碼并監(jiān)控源代碼文件的變化在開始寫一些動態(tài)的代碼之前,我們必須回答的第一個問題是,“哪部分代碼應該是動態(tài)的——整個應用程序還是僅僅某些類?從技術(shù)上來講,這部分是沒什么約束的。你可以在運行時裝載/重裝載任何java類。但是在更多的情況下,只有部分代碼需要這種靈活性。Postman的例子示范了一種典型的選擇動態(tài)類的模式。不管系統(tǒng)是如何構(gòu)成的,最終,總有諸如服務,子系統(tǒng),組件這樣的組裝塊。這些組裝塊相對獨立,通過預先定義的接口,互相暴露出了自己的功能。在接口后面,是自由變化的執(zhí)行程序,只要它符合接口定義的限制。這是我們所需要的動態(tài)類的明確的性質(zhì)。簡單說來就是:選擇實現(xiàn)類作為動態(tài)類。文章的其余部分,我們做如下關(guān)于選擇動態(tài)類的假設(shè):+被選擇的實現(xiàn)了的java接口從而暴露出自己的功能。+被選擇的動態(tài)類的執(zhí)行程序不保留任何關(guān)于其客戶的狀態(tài)信息(類似無狀態(tài)的會話bean),這樣動態(tài)類的實例可以互相替換。請注意這種假設(shè)不是必要的。這樣做只是為了讓動態(tài)代碼的實現(xiàn)稍簡單一些,以便我們可以集中更多的精力到概念和機制上去。利用心中選定好的動態(tài)類,配置源碼是很簡單的任務。圖1指出了Postman例子的文件結(jié)構(gòu)。 Figure 1. The file structure of the Postman example我們知道“src是源碼,“bin是二進制碼。一件值得注意的事情是動態(tài)代碼目錄,它包括了動態(tài)類的源文件。這兒的例子中,只有一個文件——PostmanImpl.java。bin和動態(tài)代碼的目錄是需要用來運行應用程序的,src在配置時是不需要的。檢查文件改變可以通過比較修改時間和文件大小實現(xiàn)。我們的例子中,對PostmanImpl.java的檢查是每次調(diào)用一個基于Postman接口的方法。要不,你也可以產(chǎn)生一個后臺線程來有規(guī)則地檢查文件改變。這可能會為大規(guī)模的應用程序帶來更好的性能。運行時編譯java代碼探測到源碼被改變后,我們要面臨編譯的問題。將實際的編譯工作委派給某個已經(jīng)存在的java編譯器的話,運行時編輯就不成問題了。許多java編譯器都是可用的,但在本文中,我們使用包含在Sun的 java SE平臺的javac編譯器(java SE是Sun為J2SE的新命名)。最簡單的方式,你可以僅用一條語句編譯java文件,這需要系統(tǒng)在類路徑上包含javac編譯器的tools.jar(你可以在/lib/下找到tools.jar):int errorCode = com.sun.tools.javac.Main.compile(new String[] { '-classpath', 'bin', '-d', '/temp/dynacode_classes', 'dynacode/sample/PostmanImpl.java' });類com.sun.tools.javac.Main是javac編譯器的程序接口。它為編譯java源文件提供靜態(tài)的方法。如上所述的執(zhí)行,與運行從命令行運行javac有相同的效果。它利用指定的類路徑bin編譯了源文件dynacode/sample/PostmanImpl.java,并輸出其類文件到目的文件目錄/temp/dynacode_classes。如果編譯出錯, 則會返回一個整型值。0意味著成功;其他的數(shù)字說明某些地方編譯出錯了。com.sun.tools.javac.Main類還提供了另外一個compile()方法,接受附加的PrintWriter參數(shù),如下代碼所示。如果編譯失敗的話,詳細的出錯信息將會被輸出到PrintWriter。 // Defined in com.sun.tools.javac.Main public static int compile(String[] args); public static int compile(String[] args, PrintWriter out);我想大部分的開發(fā)者應該都熟悉javac編譯器,所以這里我就講這么多了。要看更多的如何使用編譯器的信息,請參考Resources。運行時裝載/再裝載java類編譯好的類在起作用之前必須被裝載進來。Java在類裝載方面是靈活的。它定義了一個全面的類裝載機制,提供了幾個類裝載器的實現(xiàn)。(關(guān)于類裝載的更多信息,看Resources。)下面的例子代碼展示了如何裝載和再裝載類。基本的想法是利用我們自己的URLClassLoader裝載動態(tài)的類。不論何時源碼改變和重新編譯了,我們拋棄掉舊的類(因為稍后會有垃圾收集)并創(chuàng)造一個新的URLClassLoader來再次裝載該類。 // The dir contains the compiled classes. File classesDir = new File('/temp/dynacode_classes/'); // The parent classloader ClassLoader parentLoader = Postman.class.getClassLoader(); // Load class 'sample.PostmanImpl' with our own classloader. URLClassLoader loader1 = new URLClassLoader( new URL[] { classesDir.toURL() }, parentLoader); Class cls1 = loader1.loadClass('sample.PostmanImpl'); Postman postman1 = (Postman) cls1.newInstance(); /* * Invoke on postman1 ... * Then PostmanImpl.java is modified and recompiled. */ // Reload class 'sample.PostmanImpl' with a new classloader. URLClassLoader loader2 = new URLClassLoader( new URL[] { classesDir.toURL() }, parentLoader); Class cls2 = loader2.loadClass('sample.PostmanImpl'); Postman postman2 = (Postman) cls2.newInstance(); /* * Work with postman2 from now on ... * Don't worry about loader1, cls1, and postman1 * they will be garbage collected automatically. */創(chuàng)造你自己的類裝載器時注意parentLoader。基本上,父類裝載器必須提供所有的子類裝載器所需要的(依賴的)相關(guān)內(nèi)容。在例子代碼中,動態(tài)類PostmanImpl依賴接口Postman;這就是我們利用Postman的類裝載器作父類裝載器的原因。我們離完成動態(tài)代碼還差一步。回想早先介紹的例子。那里,動態(tài)類再裝載對它的調(diào)用者是透明的。但在上述例子代碼中,當代碼改變時,我們?nèi)员仨毟淖儚膒ostman1到postman2的服務實例。第四步即最后一步將移除這種手動改變的需要。連接最新的類到它的調(diào)用者對于一個靜態(tài)引用,我們?nèi)绾卧L問最新的動態(tài)類呢?顯然,一個對動態(tài)類的對象直接(通常都是這樣)的引用是不會成功的。我們需要介于客戶和動態(tài)類之間的東西——代理。(關(guān)于代理模式請參閱著名的《設(shè)計模式》一書。)這里,代理是一個作為動態(tài)類的訪問接口的功能類。客戶不能直接調(diào)用動態(tài)類;要用代理代替。然后代理指向后端動態(tài)類的調(diào)用。圖2顯示了這種協(xié)作。 Figure 2. Collaboration of the proxy當動態(tài)類重新裝載時,我們僅需更新代理和動態(tài)類之間的鏈接即可,客戶繼續(xù)用同樣的代理實例來訪問被重新轉(zhuǎn)載的類。圖3顯示了這種協(xié)作。 Figure 3. Collaboration of the proxy with reloaded dynamic class這樣,動態(tài)類的改變對它的調(diào)用者就是透明的了。Java反射API包括了一個創(chuàng)建代理的方便的工具。類java.lang.reflect.Proxy提供了靜態(tài)的方法,讓你為任何java接口創(chuàng)建代理實例。下面的例子為接口Postman創(chuàng)建了一個代理。(如果你對java.lang.reflect.Proxy不熟悉,請在繼續(xù)往下看之前看看javadoc。) InvocationHandler handler = new DynaCodeInvocationHandler(...); Postman proxy = (Postman) Proxy.newProxyInstance( Postman.class.getClassLoader(), new Class[] { Postman.class }, handler);返回的proxy是匿名類的一個對象,它與Postman接口共享了同一個類裝載器(newProxyInstance()方法的第一個參數(shù))并執(zhí)行Postman接口(第二個參數(shù))。Proxy實例的方法調(diào)用被分配給handler的invoke()方法(第三個參數(shù))。Handler的執(zhí)行程序可能看起來如下:public class DynaCodeInvocationHandler implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Get an instance of the up-to-date dynamic class Object dynacode = getUpToDateInstance(); // Forward the invocation return method.invoke(dynacode, args); }}invoke()方法獲得最新的動態(tài)類實例并且調(diào)用它。如果動態(tài)類的源碼文件被修改了的話,這可能導致源代碼的編輯和類的重新裝載。現(xiàn)在我們有了完整的Postman服務的動態(tài)代碼。客戶創(chuàng)建一個服務的代理并通過該代理調(diào)用deliverMessage()方法。代理的每次調(diào)用被分配到DynaCodeInvocationHandler類的invoke()方法。在那個方法里,將會得到可能需要源碼編譯和類的重新裝載的最新的服務實現(xiàn),然后,調(diào)用服務實現(xiàn)進行實際的處理。把它們放到一起我們講述了動態(tài)java代碼需要的所有竅門。是時候把它們放到一起來建立一些可重用的東西了。我們可以通過建立一個工具, 從而壓縮以上四個步驟,使得采用動態(tài)代碼變得更簡單。 Postman例子會依賴于這個名叫DynaCode的工具。記得PostmanApp應用程序和它的省略方法getPostman()吧?是時候展示它了:public class PostmanApp { public static void main(String[] args) throws Exception { // ...... } private static Postman getPostman() { DynaCode dynacode = new DynaCode(); dynacode.addSourceDir(new File('dynacode')); return (Postman) dynacode.newProxyInstance(Postman.class, 'sample.PostmanImpl'); }}看看getPostman()方法是如何創(chuàng)建一個動態(tài)的Postman服務、創(chuàng)建一個DynaCode實例、指定一個源目錄、返回一個某些動態(tài)執(zhí)行程序的代理的。為了使用你自己的動態(tài)java代碼,你只是需要寫三行代碼。其它事情都由內(nèi)部的DynaCode負責。自己試試吧(DynaCode的源碼包含在例子中)。我不會更進一步的講解DynaCode的細節(jié)了,但是作為我們所講的技術(shù)回顧,看看圖4的序列圖,以理解DynaCode是如何高水平工作的。 Figure 4. Sequence diagram of DynaCode. Click on thumbnail to view full-sized image.結(jié)論在這篇文章中,我介紹了動態(tài)java代碼的思想和實現(xiàn)它的步驟。我包含了諸如運行時源碼編輯,類的重裝載和代理設(shè)計模式等主題。雖然這些話題一個都不新鮮,但把它們放在一起,我們?yōu)槠胀ǖ膉ava類創(chuàng)建了一個有趣的動態(tài)的未來,它們能夠像JSP一樣在運行時被修改和更新。在Resources中提供了一個例子作為示范。它包括一個可以重用的叫DynaCode的實用程序,可以讓你的動態(tài)代碼更容易編寫。我想以討論動態(tài)代碼的價值和應用作結(jié):動態(tài)代碼可以快速響安全變化的要求。它可以被用來實現(xiàn)真實的動態(tài)服務和時時改變的業(yè)務規(guī)則,代替工作流的任務節(jié)點中使用的嵌入式腳本。動態(tài)代碼也減輕了應用程序維護和大大減少了由應用軟件重新部署引起的故障。關(guān)于作者Li Yang在2004年4月加入了IBM,獲得Rational Unified Process認證 (譯者注:RUP,統(tǒng)一軟件過程。是一個完善的軟件開發(fā)過程框架,具有若干種即裝即用的實例,將項目管理、商業(yè)建模、分析與設(shè)計等,統(tǒng)一到一致的、貫穿整個開發(fā)周期的處理過程。),是一個需求管理,和面向?qū)ο蠓治雠c設(shè)計的IBM Rational顧問。他在服務器端技術(shù),web架構(gòu),java EE項目和java SE系統(tǒng)庫開發(fā)方面有豐富的經(jīng)驗。資源+Matrix:http://www.matrix.org.cn+源代碼: http://www.javaworld.com/javaworld/jw-06-2006/dynamic/jw-0612-dynamic.zip+Javac compiler documentation: http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/javac.html Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd 編寫可以響應運行時變化的代碼摘要你曾經(jīng)希望你的java代碼能夠像JSP一樣是動態(tài)的嗎?它可以在運行時被修改和重新編譯,同時你的應用程序自動更新。本文闡述了如何讓你的代碼動態(tài)化。同樣的,你的一些源代碼將會被?
標簽:
Java
相關(guān)文章:
1. Spring @Primary和@Qualifier注解原理解析2. 詳解php如何合并身份證正反面圖片為一張圖片3. 如何用python識別滑塊驗證碼中的缺口4. php設(shè)計模式之模板模式實例分析【星際爭霸游戲案例】5. AJAX實現(xiàn)省市縣三級聯(lián)動效果6. ASP.NET MVC視圖頁使用jQuery傳遞異步數(shù)據(jù)的幾種方式詳解7. Java基于redis和mysql實現(xiàn)簡單的秒殺(附demo)8. SpringBoot+SpringCache實現(xiàn)兩級緩存(Redis+Caffeine)9. HTML iframe標簽用法案例詳解10. ASP.NET 2.0頁面框架的幾處變化
排行榜
