前言
java反射,最常用的Class.forName()方法。做畢設的時候,接收到代碼字符串,通過 JavaCompiler將代碼字符串生成A.class文件(存放在classpath下,也就是eclipse項目中的bin目錄里),然后通過java反射機制,獲取main方法并執行。.class文件名稱固定。當 A.class文件更新的時候,問題出現了,main方法的執行結果總和第一次的執行結果相同。
程序流程
代碼提交->接收代碼->編譯成A.class文件->java反射->main方法執行
具體代碼參考:http://www.cnblogs.com/hujunzheng/p/5203067.html
問題原因
類加載器的委托機制!說到這里,不得不介紹一下java的類加載器。
java虛擬機中的類加載器
java虛擬機中可以安裝多個類加載器,系統默認三個主要的類加載器,每個類負責加載特定位置的類: BootStrap,ExtClassLoader,AppClassLoader
類加載器也是Java類,因為Java類的類加載器本身也是要被類加載器加載的,顯然必須有第一個類加載器不是Java類,這個正是BootStrap,使用C/C++代碼寫的,已經封裝到JVM內核中了,而ExtClassLoader和AppClassLoader是Java類。
類加載器的屬性結構圖
盜圖一張:
由此得到結論
首先我的A.class文件更新了,接著調用Class.forName()[我想的是重新加載一下字節碼文件對象],然后最終由AppClassLoader去加載,其中有一個函數很重要,就是loadClass(), 看一下這個函數的源碼,如下:
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{ //加上鎖,同步處理,因為可能是多線程在加載類 synchronized (getClassLoadingLock(name)) { //檢查,是否該類已經加載過了,如果加載過了,就不加載了 Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //如果自定義的類加載器的parent不為null,就調用parent的loadClass進行加載類 if (parent != null) { c = parent.loadClass(name, false); } else { //如果自定義的類加載器的parent為null,就調用findBootstrapClass方法查找類,就是Bootstrap類加載器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //如果parent加載類失敗,就調用自己的findClass方法進行類加載 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
如果同名的.class文件之前加載了就不會在加載了。。。
解決辦法 用戶自定義類加載器
想法1: 重寫loadClass()這個函數,無論是否加載過.class問價,都重新加載。
@Overridepublic java.lang.Class<?> loadClass(String name) throws ClassNotFoundException {System.out.println(name);byte[] data = loaderClassData(name);return this.defineClass(name, data, 0, data.length);};
但是竟然出錯了,至今還沒有搞明白... Main是我要加載的類,loadClass()函數執行了兩次,第二次不知道怎么調用的。。。?有誰知到,告訴我一下,謝了!
Main java.lang.Object java.io.FileNotFoundException: java\lang\Object.class (系統找不到指定的路徑。)at java.io.FileInputStream.open(Native Method)at java.io.FileInputStream.<init>(Unknown Source)at com.ds.tools.MyClassLoader.loaderClassData(MyClassLoader.java:53)at com.ds.tools.MyClassLoader.loadClass(MyClassLoader.java:78)at java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClassCond(Unknown Source)at java.lang.ClassLoader.defineClass(Unknown Source)at java.lang.ClassLoader.defineClass(Unknown Source)at com.ds.tools.MyClassLoader.loadClass(MyClassLoader.java:79)at com.ds.tools.MyClassLoader.main(MyClassLoader.java:96)
想法2: 只能默默的重寫findClass()方法了, loadClass()方法中會調用這個函數,為了避過AppClassLoader檢查類是否已經加載過了,我把A.class的生成位置放到了項目根目錄下的myClass目錄中,這樣MyClassLoader委托AppClassLoader對A.class進行加載時,在當前的classpath下找不到對應的類,無法完成類的加載(同樣BootStrapLoader和ExtClassLoader都不會找到),最終是我們自定的類加載器完成類的加載,代碼如下:
public class MyClassLoader extends ClassLoader {//類加載器名稱private String loaderName;//加載類的路徑private String path = "";private final String fileType = ".class";public MyClassLoader(String loaderName){//讓系統類加載器成為該 類加載器的父加載器super();this.loaderName = loaderName;}public MyClassLoader(ClassLoader parent, String loaderName){//顯示指定該類加載器的父加載器super(parent);this.loaderName = loaderName;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}@Overridepublic String toString() {return this.loaderName;}/*** 獲取.class文件的字節數組* @param name* @return*/private byte[] loaderClassData(String name){InputStream is = null;byte[] data = null;ByteArrayOutputStream baos = new ByteArrayOutputStream();name = name.replace(".", "/");try {is = new FileInputStream(new File(path + name + fileType));int c = 0;while(-1 != (c = is.read())){baos.write(c);}data = baos.toByteArray();} catch (Exception e) {e.printStackTrace();} finally{try {if(is != null)is.close();if(baos != null)baos.close();} catch (IOException e) {e.printStackTrace();}}return data;}/*** 獲取Class對象*/@Overridepublic Class<?> findClass(String name) throws ClassNotFoundException{byte[] data = loaderClassData(name);return this.defineClass(name, data, 0, data.length);}public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {for(int i=0; i<5; i++){MyClassLoader loader1 = new MyClassLoader("MyClassLoader");//String path = new File(MyClassLoader.getSystemClassLoader().getResource("").getPath()).getParent();loader1.setPath("myClass/");Class<?> clazz = loader1.loadClass("Main");System.out.println(clazz.getName());}} }
?