文章詳情頁(yè)
為你的應(yīng)用程序添加動(dòng)態(tài)Java代碼
瀏覽:80日期:2024-06-05 16:43:14
內(nèi)容: 編寫可以響應(yīng)運(yùn)行時(shí)變化的代碼摘要你曾經(jīng)希望你的java代碼能夠像JSP一樣是動(dòng)態(tài)的嗎?它可以在運(yùn)行時(shí)被修改和重新編譯,同時(shí)你的應(yīng)用程序自動(dòng)更新。本文闡述了如何讓你的代碼動(dòng)態(tài)化。同樣的,你的一些源代碼將會(huì)被直接部署,而不是編譯好的字節(jié)碼。這些源代碼的任何改變都將引起這些源代碼的再編譯和類的重新裝載。然后你的應(yīng)用程序就會(huì)運(yùn)行在新的類上,用戶將立即看到這種改變。本文不僅講述了運(yùn)行時(shí)源碼編輯和類裝載,而且還提出一個(gè)將動(dòng)態(tài)代碼與其調(diào)用者分離的設(shè)計(jì)方案。調(diào)用者保存對(duì)動(dòng)態(tài)代碼的一個(gè)靜態(tài)引用,而不管動(dòng)態(tài)代碼運(yùn)行時(shí)如何再次裝載,調(diào)用者總能訪問(wèn)最新的類且不用更新引用。這樣,動(dòng)態(tài)代碼改變對(duì)客戶是透明的。JSP是一種比servlets更有彈性的技術(shù),因?yàn)樗梢皂憫?yīng)運(yùn)行時(shí)的動(dòng)態(tài)改變。你可以想象一個(gè)普通的java類也有這種動(dòng)態(tài)的能力嗎?如果你能修改服務(wù)的執(zhí)行而不用重新部署和更新應(yīng)用程序,將會(huì)是很有趣的。文章說(shuō)明了如何編寫動(dòng)態(tài)的代碼。它討論運(yùn)行時(shí)源碼編輯,類的再裝載,和讓動(dòng)態(tài)類的修改對(duì)它的調(diào)用者透明的代理設(shè)計(jì)模式。版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請(qǐng)務(wù)必保留以下作者信息和鏈接作者: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;動(dòng)態(tài)代碼一個(gè)動(dòng)態(tài)java代碼的例子讓我們以一個(gè)動(dòng)態(tài)java代碼的例子開(kāi)始來(lái)闡釋真正的動(dòng)態(tài)代碼意味著什么,為下文的討論做鋪墊。請(qǐng)?jiān)谠创a中找到這個(gè)例子完整的源代碼。這個(gè)例子是一個(gè)簡(jiǎn)單的依靠名叫Postman的服務(wù)的java應(yīng)用程序。Postman服務(wù)是一個(gè)java接口,僅包括一個(gè)方法,deliverMessage():public interface Postman { void deliverMessage(String msg);}這項(xiàng)服務(wù)的簡(jiǎn)單執(zhí)行是向控制臺(tái)打印消息。執(zhí)行類是動(dòng)態(tài)的代碼。這個(gè)類,PostmanImpl,僅是一個(gè)普通的 java類,如果不是展開(kāi)它的源碼代替它的已編譯好的二進(jìn)制碼: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服務(wù)的應(yīng)用程序如下。在main()方法里,循環(huán)從控制行讀取消息并通過(guò)Postman服務(wù)進(jìn)行傳遞: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í)行這個(gè)應(yīng)用程序,輸入一些信息,你將看到控制臺(tái)輸出如下(你可以下載該例子并自行運(yùn)行): 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)在讓我們來(lái)看看動(dòng)態(tài)的東西。 不要停止應(yīng)用程序,讓我們修改PostmanImpl的源碼。新的執(zhí)行程序?qū)?huì)把所有的信息輸出到一個(gè)文本文件,而不是控制臺(tái)。// 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(); }}回到應(yīng)用程序,輸入更多信息,將會(huì)發(fā)生什么呢?是的,信息都到文本文件里去了。看控制臺(tái): 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)了,說(shuō)明類PostmanImpl被再次編譯和裝載了。如果你檢查文本文件msg.txt(在working目錄下),你會(huì)看到如下內(nèi)容: Postman I wanna go to the text file. Postman me too!令人驚訝,對(duì)吧?我們可以在運(yùn)行時(shí)更新Postman服務(wù),這種改變對(duì)應(yīng)用程序是完全透明的。(注意應(yīng)用程序使用了相同的Postman實(shí)例來(lái)訪問(wèn)各種執(zhí)行程序的版本。)實(shí)現(xiàn)動(dòng)態(tài)代碼的四個(gè)步驟讓我來(lái)揭示在這種表象后面到底發(fā)生了什么。基本上,構(gòu)成動(dòng)態(tài)代碼分四個(gè)步驟:+部署選擇的部分源代碼并監(jiān)控源代碼文件的變化+在運(yùn)行時(shí)編譯java代碼+在運(yùn)行時(shí)裝載/重裝載java類+將最新的類鏈接給它的調(diào)用者部署選擇的部分源代碼并監(jiān)控源代碼文件的變化在開(kāi)始寫一些動(dòng)態(tài)的代碼之前,我們必須回答的第一個(gè)問(wèn)題是,“哪部分代碼應(yīng)該是動(dòng)態(tài)的——整個(gè)應(yīng)用程序還是僅僅某些類?從技術(shù)上來(lái)講,這部分是沒(méi)什么約束的。你可以在運(yùn)行時(shí)裝載/重裝載任何java類。但是在更多的情況下,只有部分代碼需要這種靈活性。Postman的例子示范了一種典型的選擇動(dòng)態(tài)類的模式。不管系統(tǒng)是如何構(gòu)成的,最終,總有諸如服務(wù),子系統(tǒng),組件這樣的組裝塊。這些組裝塊相對(duì)獨(dú)立,通過(guò)預(yù)先定義的接口,互相暴露出了自己的功能。在接口后面,是自由變化的執(zhí)行程序,只要它符合接口定義的限制。這是我們所需要的動(dòng)態(tài)類的明確的性質(zhì)。簡(jiǎn)單說(shuō)來(lái)就是:選擇實(shí)現(xiàn)類作為動(dòng)態(tài)類。文章的其余部分,我們做如下關(guān)于選擇動(dòng)態(tài)類的假設(shè):+被選擇的實(shí)現(xiàn)了的java接口從而暴露出自己的功能。+被選擇的動(dòng)態(tài)類的執(zhí)行程序不保留任何關(guān)于其客戶的狀態(tài)信息(類似無(wú)狀態(tài)的會(huì)話bean),這樣動(dòng)態(tài)類的實(shí)例可以互相替換。請(qǐng)注意這種假設(shè)不是必要的。這樣做只是為了讓動(dòng)態(tài)代碼的實(shí)現(xiàn)稍簡(jiǎn)單一些,以便我們可以集中更多的精力到概念和機(jī)制上去。利用心中選定好的動(dòng)態(tài)類,配置源碼是很簡(jiǎn)單的任務(wù)。圖1指出了Postman例子的文件結(jié)構(gòu)。 Figure 1. The file structure of the Postman example我們知道“src是源碼,“bin是二進(jìn)制碼。一件值得注意的事情是動(dòng)態(tài)代碼目錄,它包括了動(dòng)態(tài)類的源文件。這兒的例子中,只有一個(gè)文件——PostmanImpl.java。bin和動(dòng)態(tài)代碼的目錄是需要用來(lái)運(yùn)行應(yīng)用程序的,src在配置時(shí)是不需要的。檢查文件改變可以通過(guò)比較修改時(shí)間和文件大小實(shí)現(xiàn)。我們的例子中,對(duì)PostmanImpl.java的檢查是每次調(diào)用一個(gè)基于Postman接口的方法。要不,你也可以產(chǎn)生一個(gè)后臺(tái)線程來(lái)有規(guī)則地檢查文件改變。這可能會(huì)為大規(guī)模的應(yīng)用程序帶來(lái)更好的性能。運(yùn)行時(shí)編譯java代碼探測(cè)到源碼被改變后,我們要面臨編譯的問(wèn)題。將實(shí)際的編譯工作委派給某個(gè)已經(jīng)存在的java編譯器的話,運(yùn)行時(shí)編輯就不成問(wèn)題了。許多java編譯器都是可用的,但在本文中,我們使用包含在Sun的 java SE平臺(tái)的javac編譯器(java SE是Sun為J2SE的新命名)。最簡(jiǎn)單的方式,你可以僅用一條語(yǔ)句編譯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í)行,與運(yùn)行從命令行運(yùn)行javac有相同的效果。它利用指定的類路徑bin編譯了源文件dynacode/sample/PostmanImpl.java,并輸出其類文件到目的文件目錄/temp/dynacode_classes。如果編譯出錯(cuò), 則會(huì)返回一個(gè)整型值。0意味著成功;其他的數(shù)字說(shuō)明某些地方編譯出錯(cuò)了。com.sun.tools.javac.Main類還提供了另外一個(gè)compile()方法,接受附加的PrintWriter參數(shù),如下代碼所示。如果編譯失敗的話,詳細(xì)的出錯(cuò)信息將會(huì)被輸出到PrintWriter。 // Defined in com.sun.tools.javac.Main public static int compile(String[] args); public static int compile(String[] args, PrintWriter out);我想大部分的開(kāi)發(fā)者應(yīng)該都熟悉javac編譯器,所以這里我就講這么多了。要看更多的如何使用編譯器的信息,請(qǐng)參考Resources。運(yùn)行時(shí)裝載/再裝載java類編譯好的類在起作用之前必須被裝載進(jìn)來(lái)。Java在類裝載方面是靈活的。它定義了一個(gè)全面的類裝載機(jī)制,提供了幾個(gè)類裝載器的實(shí)現(xiàn)。(關(guān)于類裝載的更多信息,看Resources。)下面的例子代碼展示了如何裝載和再裝載類。基本的想法是利用我們自己的URLClassLoader裝載動(dòng)態(tài)的類。不論何時(shí)源碼改變和重新編譯了,我們拋棄掉舊的類(因?yàn)樯院髸?huì)有垃圾收集)并創(chuàng)造一個(gè)新的URLClassLoader來(lái)再次裝載該類。 // 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)造你自己的類裝載器時(shí)注意parentLoader。基本上,父類裝載器必須提供所有的子類裝載器所需要的(依賴的)相關(guān)內(nèi)容。在例子代碼中,動(dòng)態(tài)類PostmanImpl依賴接口Postman;這就是我們利用Postman的類裝載器作父類裝載器的原因。我們離完成動(dòng)態(tài)代碼還差一步。回想早先介紹的例子。那里,動(dòng)態(tài)類再裝載對(duì)它的調(diào)用者是透明的。但在上述例子代碼中,當(dāng)代碼改變時(shí),我們?nèi)员仨毟淖儚膒ostman1到postman2的服務(wù)實(shí)例。第四步即最后一步將移除這種手動(dòng)改變的需要。連接最新的類到它的調(diào)用者對(duì)于一個(gè)靜態(tài)引用,我們?nèi)绾卧L問(wèn)最新的動(dòng)態(tài)類呢?顯然,一個(gè)對(duì)動(dòng)態(tài)類的對(duì)象直接(通常都是這樣)的引用是不會(huì)成功的。我們需要介于客戶和動(dòng)態(tài)類之間的東西——代理。(關(guān)于代理模式請(qǐng)參閱著名的《設(shè)計(jì)模式》一書(shū)。)這里,代理是一個(gè)作為動(dòng)態(tài)類的訪問(wèn)接口的功能類。客戶不能直接調(diào)用動(dòng)態(tài)類;要用代理代替。然后代理指向后端動(dòng)態(tài)類的調(diào)用。圖2顯示了這種協(xié)作。 Figure 2. Collaboration of the proxy當(dāng)動(dòng)態(tài)類重新裝載時(shí),我們僅需更新代理和動(dòng)態(tài)類之間的鏈接即可,客戶繼續(xù)用同樣的代理實(shí)例來(lái)訪問(wèn)被重新轉(zhuǎn)載的類。圖3顯示了這種協(xié)作。 Figure 3. Collaboration of the proxy with reloaded dynamic class這樣,動(dòng)態(tài)類的改變對(duì)它的調(diào)用者就是透明的了。Java反射API包括了一個(gè)創(chuàng)建代理的方便的工具。類java.lang.reflect.Proxy提供了靜態(tài)的方法,讓你為任何java接口創(chuàng)建代理實(shí)例。下面的例子為接口Postman創(chuàng)建了一個(gè)代理。(如果你對(duì)java.lang.reflect.Proxy不熟悉,請(qǐng)?jiān)诶^續(xù)往下看之前看看javadoc。) InvocationHandler handler = new DynaCodeInvocationHandler(...); Postman proxy = (Postman) Proxy.newProxyInstance( Postman.class.getClassLoader(), new Class[] { Postman.class }, handler);返回的proxy是匿名類的一個(gè)對(duì)象,它與Postman接口共享了同一個(gè)類裝載器(newProxyInstance()方法的第一個(gè)參數(shù))并執(zhí)行Postman接口(第二個(gè)參數(shù))。Proxy實(shí)例的方法調(diào)用被分配給handler的invoke()方法(第三個(gè)參數(shù))。Handler的執(zhí)行程序可能看起來(lái)如下: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()方法獲得最新的動(dòng)態(tài)類實(shí)例并且調(diào)用它。如果動(dòng)態(tài)類的源碼文件被修改了的話,這可能導(dǎo)致源代碼的編輯和類的重新裝載。現(xiàn)在我們有了完整的Postman服務(wù)的動(dòng)態(tài)代碼。客戶創(chuàng)建一個(gè)服務(wù)的代理并通過(guò)該代理調(diào)用deliverMessage()方法。代理的每次調(diào)用被分配到DynaCodeInvocationHandler類的invoke()方法。在那個(gè)方法里,將會(huì)得到可能需要源碼編譯和類的重新裝載的最新的服務(wù)實(shí)現(xiàn),然后,調(diào)用服務(wù)實(shí)現(xiàn)進(jìn)行實(shí)際的處理。把它們放到一起我們講述了動(dòng)態(tài)java代碼需要的所有竅門。是時(shí)候把它們放到一起來(lái)建立一些可重用的東西了。我們可以通過(guò)建立一個(gè)工具, 從而壓縮以上四個(gè)步驟,使得采用動(dòng)態(tài)代碼變得更簡(jiǎn)單。 Postman例子會(huì)依賴于這個(gè)名叫DynaCode的工具。記得PostmanApp應(yīng)用程序和它的省略方法getPostman()吧?是時(shí)候展示它了: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)建一個(gè)動(dòng)態(tài)的Postman服務(wù)、創(chuàng)建一個(gè)DynaCode實(shí)例、指定一個(gè)源目錄、返回一個(gè)某些動(dòng)態(tài)執(zhí)行程序的代理的。為了使用你自己的動(dòng)態(tài)java代碼,你只是需要寫三行代碼。其它事情都由內(nèi)部的DynaCode負(fù)責(zé)。自己試試吧(DynaCode的源碼包含在例子中)。我不會(huì)更進(jìn)一步的講解DynaCode的細(xì)節(jié)了,但是作為我們所講的技術(shù)回顧,看看圖4的序列圖,以理解DynaCode是如何高水平工作的。 Figure 4. Sequence diagram of DynaCode. Click on thumbnail to view full-sized image.結(jié)論在這篇文章中,我介紹了動(dòng)態(tài)java代碼的思想和實(shí)現(xiàn)它的步驟。我包含了諸如運(yùn)行時(shí)源碼編輯,類的重裝載和代理設(shè)計(jì)模式等主題。雖然這些話題一個(gè)都不新鮮,但把它們放在一起,我們?yōu)槠胀ǖ膉ava類創(chuàng)建了一個(gè)有趣的動(dòng)態(tài)的未來(lái),它們能夠像JSP一樣在運(yùn)行時(shí)被修改和更新。在Resources中提供了一個(gè)例子作為示范。它包括一個(gè)可以重用的叫DynaCode的實(shí)用程序,可以讓你的動(dòng)態(tài)代碼更容易編寫。我想以討論動(dòng)態(tài)代碼的價(jià)值和應(yīng)用作結(jié):動(dòng)態(tài)代碼可以快速響安全變化的要求。它可以被用來(lái)實(shí)現(xiàn)真實(shí)的動(dòng)態(tài)服務(wù)和時(shí)時(shí)改變的業(yè)務(wù)規(guī)則,代替工作流的任務(wù)節(jié)點(diǎn)中使用的嵌入式腳本。動(dòng)態(tài)代碼也減輕了應(yīng)用程序維護(hù)和大大減少了由應(yīng)用軟件重新部署引起的故障。關(guān)于作者Li Yang在2004年4月加入了IBM,獲得Rational Unified Process認(rèn)證 (譯者注:RUP,統(tǒng)一軟件過(guò)程。是一個(gè)完善的軟件開(kāi)發(fā)過(guò)程框架,具有若干種即裝即用的實(shí)例,將項(xiàng)目管理、商業(yè)建模、分析與設(shè)計(jì)等,統(tǒng)一到一致的、貫穿整個(gè)開(kāi)發(fā)周期的處理過(guò)程。),是一個(gè)需求管理,和面向?qū)ο蠓治雠c設(shè)計(jì)的IBM Rational顧問(wèn)。他在服務(wù)器端技術(shù),web架構(gòu),java EE項(xiàng)目和java SE系統(tǒng)庫(kù)開(kāi)發(fā)方面有豐富的經(jīng)驗(yàn)。資源+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 編寫可以響應(yīng)運(yùn)行時(shí)變化的代碼摘要你曾經(jīng)希望你的java代碼能夠像JSP一樣是動(dòng)態(tài)的嗎?它可以在運(yùn)行時(shí)被修改和重新編譯,同時(shí)你的應(yīng)用程序自動(dòng)更新。本文闡述了如何讓你的代碼動(dòng)態(tài)化。同樣的,你的一些源代碼將會(huì)被?
標(biāo)簽:
Java
相關(guān)文章:
1. ASP動(dòng)態(tài)網(wǎng)頁(yè)制作技術(shù)經(jīng)驗(yàn)分享2. jsp文件下載功能實(shí)現(xiàn)代碼3. asp.net core項(xiàng)目授權(quán)流程詳解4. 在JSP中使用formatNumber控制要顯示的小數(shù)位數(shù)方法5. CSS3實(shí)現(xiàn)動(dòng)態(tài)翻牌效果 仿百度貼吧3D翻牌一次動(dòng)畫(huà)特效6. XMLHTTP資料7. ASP常用日期格式化函數(shù) FormatDate()8. html中的form不提交(排除)某些input 原創(chuàng)9. CSS3中Transition屬性詳解以及示例分享10. ASP基礎(chǔ)入門第八篇(ASP內(nèi)建對(duì)象Application和Session)
排行榜
