V1.1
修改了主程式,調用帶參數的建構函式
package com.ailk;import java.lang.reflect.*;import java.util.ArrayList;import java.util.List;import com.ailk.dynamic.Demo;public class DemoMain { static public void main( String args[] ) throws Exception { String progClass ="com.ailk.dynamic.Demo"; // 建立CompilingClassLoader Class c = Class.forName(progClass, true, new CompilingClassLoader()); //DemoInterface i=(DemoInterface)c.newInstance(); //cl1和cl2是兩個不同的ClassLoader ClassLoader cl1=c.getClassLoader(); ClassLoader cl2=Demo.class.getClassLoader(); ClassLoader cl3=DemoInterface.class.getClassLoader(); int ii=0; List<DemoInterface> objList=new ArrayList(); while(true){ ii++; CompilingClassLoader ccl = new CompilingClassLoader(); // 通過CCL載入主函數類。 Class clas = ccl.loadClass( progClass,true); try{ Constructor c1=c.getDeclaredConstructor(new Class[]{String.class}); c1.setAccessible(true); DemoInterface a1=(DemoInterface)c1.newInstance(new Object[]{"Demo"}); }catch(NoSuchMethodException e){ System.out.println("建構函式不存在"); e.printStackTrace(); } DemoInterface instance=null; try{ Constructor c0=clas.getDeclaredConstructor(); c0.setAccessible(true); instance=(DemoInterface)c0.newInstance(); }catch(NoSuchMethodException e){ System.out.println("建構函式不存在"); e.printStackTrace(); } ccl=null;//這裡講主動釋放cc1 //DemoInterface instance=(DemoInterface)clas.newInstance(); if (instance!=null) { objList.add(instance); instance.print("demo"); // 利用反射調用它的函數和傳遞參數。 // 產生一個代表主函數的參數類型的類對象。 Class mainArgType[] = { String.class }; //在類中找函數。 Method method = clas.getMethod( "print", mainArgType ); Object[] argsArray={"Demo"}; //調用方法。 method.invoke(instance, argsArray ); } if (ii>20) { ii=0; objList.clear(); } Thread.sleep(500); //強制gc,只有objList清空後 CompilingClassLoader的執行個體才會釋放。 //因為只有由CompilingClassLoader載入的class的執行個體全部釋放後,CompilingClassLoader才能被釋放 System.gc(); } }}
V1.0
在運行過程住動態編譯並重新載入Class
需要繼承ClassLoader ,沒有不同的CompilingClassLoader
載入的class是不相同的不能相互轉換。
對於CompilingClassLoader 的執行個體,只是簡單的賦null是會自動釋放的,只有由其載入的class的全部執行個體都釋放後,
CompilingClassLoader 的執行個體才會釋放。
我在代碼裡加入了protected void finalize()來檢測執行個體何時釋放。
在reload/com/ailk/dynamic中放一個要動態重載的Java程式,這裡我們把測試類別放這個目錄。
代理類
package com.ailk;
public interface DemoInterface {
public void print( String args);
}測試類別package com.ailk.dynamic;
import com.ailk.DemoInterface;
public class Demo implements DemoInterface {
@Override
public void print( String args){
}
}
主程式package com.ailk;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import com.ailk.dynamic.Demo;
public class DemoMain {
static public void main( String args[] ) throws Exception {
String progClass ="com.ailk.dynamic.Demo";
// 建立CompilingClassLoader
Class c = Class.forName(progClass, true, new CompilingClassLoader());
DemoInterface i=(DemoInterface)c.newInstance();
//cl1和cl2是兩個不同的ClassLoader
ClassLoader cl1=c.getClassLoader();
ClassLoader cl2=Demo.class.getClassLoader();
ClassLoader cl3=DemoInterface.class.getClassLoader();
int ii=0;
List<DemoInterface> objList=new ArrayList();
while(true){
ii++;
CompilingClassLoader ccl = new CompilingClassLoader();
// 通過CCL載入主函數類。
Class clas = ccl.loadClass( progClass,true);
// 利用反射調用它的函數和傳遞參數。
// 產生一個代表主函數的參數類型的類對象。
Class mainArgType[] = { String.class };
// 在類中找函數。
Method method = clas.getMethod( "print", mainArgType );
Object[] argsArray={"Demo"};
//調用方法。
method.invoke( clas.newInstance(), argsArray );
ccl=null;//這裡講主動釋放cc1
DemoInterface instance=(DemoInterface)clas.newInstance();
objList.add(instance);
instance.print("demo");
if (ii>20)
{
ii=0;
objList.clear();
}
Thread.sleep(500);
//強制gc,只有objList清空後 CompilingClassLoader的執行個體才會釋放。
//因為只有由CompilingClassLoader載入的class的執行個體全部釋放後,CompilingClassLoader才能被釋放
System.gc();
}
}
}下面的這個類就是我們自己的一個CompilingClassLoader 。其功能是從當前類的路徑中的目錄的reload中載入class檔案,如果其Java檔案有更新則重新編譯然後再載入,注意,在reload的目錄中只放如需要重新載入的類,其他的不要放到這個目錄中,特別是代理介面。如果代理介面也放到這個目錄中的相應的目錄下的話,CompilingClassLoader 就會將其載入,那麼 我們在main程式的DemoInterface instance=(DemoInterface)clas.newInstance()行就會發生java.lang.ClassCastException 錯誤。
package com.ailk;import java.io.*;/* CompilingClassLoader動態編譯Java源檔案。它檢查.class檔案是否存在,.class檔案是 否比源檔案陳舊。 */public class CompilingClassLoader extends ClassLoader {protected void finalize() {System.out.println("finalize this:"+this);try {super.finalize();} catch (Throwable e) {// TODO Auto-generated catch blocke.printStackTrace();} }// 指定一個檔案名稱,從磁碟讀取整個檔案內容,返回位元組數組。private byte[] getBytes(String filename) throws IOException {// 獲得檔案大小。File file = new File(filename);long len = file.length();// 建立一個數組剛好可以存放檔案的內容。byte raw[] = new byte[(int) len];// 開啟檔案FileInputStream fin = new FileInputStream(file);// 讀取所有內容,如果沒法讀取,表示發生了一個錯誤。int r = fin.read(raw);if (r != len)throw new IOException("Can't read all, " + r + " != " + len);// 別忘了關閉檔案。fin.close();// 返回這個數組。return raw;}// 產生一個進程來編譯指定的Java源檔案,制定檔案參數.如果編譯成功返回true,否者,// 返回false。private boolean compile(String javaFile) throws IOException {// 顯示當前進度System.out.println("CCL: Compiling " + javaFile + "...");// 啟動編譯器Process p = Runtime.getRuntime().exec("javac -classpath " + CompilingClassLoader.class.getResource("/").getPath()+ " -Xlint:unchecked " + javaFile);// 等待編譯結束try {p.waitFor();} catch (InterruptedException ie) {System.out.println(ie);}// 檢查返回碼,看編譯是否出錯。int ret = p.exitValue();// 返回編譯是否成功。return ret == 0;}// 類載入器的核心代碼 -載入類在需要的時候自動編譯源檔案。public Class loadClass(String name, boolean resolve)throws ClassNotFoundException {// if (!name.startsWith("com.ailk.dynamic")){// return getParent().loadClass(name);// }// 我們的目的是獲得一個類對象。Class clas = null;// 首先,檢查是否已經出理過這個類。clas = findLoadedClass(name);if (clas != null)return clas;//if (clas == null) {//try {//if (getParent() != null) {//clas = super.findClass(name);//} else {//clas = findSystemClass(name);//}//} catch (ClassNotFoundException e) {//// If still not found, then invoke findClass in order//// to find the class.////clas = findClass(name);//}//}// System.out.println( "findLoadedClass: "+clas );// 通過類名獲得路徑名 比如:java.lang.Object => java/lang/ObjectString fileStub = name.replace('.', '/');// 構建指向源檔案和類檔案的對象。String javaFilename = CompilingClassLoader.class.getResource("/").getPath() + "reload/"+ fileStub + ".java";//System.out.println(javaFilename);String classFilename = CompilingClassLoader.class.getResource("/").getPath() + "reload/"+ fileStub + ".class";//System.out.println(classFilename);File javaFile = new File(javaFilename);File classFile = new File(classFilename);// System.out.println( "j "+javaFile.lastModified()+" c "// +classFile.lastModified() );// 首先,判斷是否需要編譯。如果源檔案存在而類檔案不存在,或者都存在,但是源檔案// 較新,說明需要編譯。boolean javaExists = javaFile.exists();boolean classExists = classFile.exists();if (javaFile.exists()&& (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) {try {// 編譯,如果編譯失敗,我們必須聲明失敗原因(僅僅使用陳舊的類是不夠的)。if (!compile(javaFilename) || !classFile.exists()) {throw new ClassNotFoundException("Compile failed: "+ javaFilename);}} catch (IOException ie) {// 可能編譯時間出現IO錯誤。throw new ClassNotFoundException(ie.toString());}}// 確保已經正確編譯或者不需要編譯,我們開始載入原始位元組。try {// 讀取位元組。byte raw[] = getBytes(classFilename);// 轉化為類對象clas = defineClass(name, raw, 0, raw.length);System.out.println("load class:"+classFilename+" classloader is:"+this);} catch (IOException ie) {// 這裡並不表示失敗,可能我們處理的類在本地類庫中,如java.lang.Object。}// System.out.println( "defineClass: "+clas );// 可能在類庫中,以預設的方式載入。if (clas == null) {clas = findSystemClass(name);//System.out.println("use define class:"+name);}// System.out.println( "findSystemClass: "+clas );// 如果參數resolve為true,根據需要解釋類。if (resolve && clas != null)resolveClass(clas);// 如果還沒有獲得類,說明出錯了。if (clas == null)throw new ClassNotFoundException(name);// 否則,返回這個類對象。return clas;}}