實現自己的類加載時,重寫方法loadClass與findClass的區別

   Java中的類加載器,有啓動類加載器(Bootstrap Classloader)、擴展類加載器(Launcher$ExtClassLoader)、應用程序類加載器(Launcher$AppClassLoader),用戶還可以實現自定義的類加載器,見下圖:

 

    類加載的這種關係稱爲雙親委派模式,需要注意的是他們之間不是繼承關係,而是組合關係,在執行類加載的動作時,首先都是交給父類去加載,如果父類無法加載再交給子類去完成,直到調用用戶自定義的類加載器去加載,如果全部都無法加載,就會拋出ClassNotFoundException。

    Launcher$ExtClassLoader和Launcher$AppClassLoader都是URLClassLoader的子類,但是他們的實現又是有一點不同的,Launcher$ExtClassLoader的實現是遵循的雙親委派模型,它重寫的是findClass方法,如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. protected Class findClass(String paramString) throws ClassNotFoundException {  
  2.   
  3.     DownloadManager.getBootClassPathEntryForClass(paramString);  
  4.   
  5.     return super.findClass(paramString);  
  6.   
  7. }  
    而Launcher$AppClassLoader的實現是沒有遵循雙親委派模型的,它重的是loadClass方法,以下是AppClassLoader中的loadClass的源碼:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public synchronized Class loadClass(String paramString, boolean paramBoolean) throws ClassNotFoundException {  
  2.   
  3.     DownloadManager.getBootClassPathEntryForClass(paramString);  
  4.   
  5.     int i = paramString.lastIndexOf(46);  
  6.   
  7.     if (i != -1) {  
  8.   
  9.         SecurityManager localSecurityManager =  
  10.   
  11.                System.getSecurityManager();  
  12.   
  13.         if (localSecurityManager != null) {  
  14.   
  15.             localSecurityManager.  
  16.   
  17.                checkPackageAccess(paramString.substring(0, i));  
  18.   
  19.         }  
  20.   
  21.     }  
  22.   
  23.     return super.loadClass(paramString, paramBoolean);  
  24.   
  25. }  
    注:以上的源碼是通過JD反編譯過來的,可能會和實際的源碼會有一點點不一樣,JDK的版本是HotSpot 6U24。

    他們的實現方式爲什麼會不同呢?因爲Launcher$ExtClassLoader加載的類是屬於$JRE_HOME/lib/ext下面(也可能通過系統變量java.ext.dir指定路徑)的擴展類,Sun公司肯定不會寫兩個或者多個具有相同全限定名的類、但是功能卻不相同的類的,如果真有這樣的類存在,那JVM的執行肯定就會有問題了,這在它的控制範圍之內的事情。而Launcher$AppClassLoader是用於加載各個不同應用下面的類,同一個JVM中可以同時存在多個應用,如同一個Tomcat中可以同時存在多個Web應用,而這些應用可能是來自不同的開發方,他們之間彼此可能都不知道是誰,但是他們寫的類卻可能具有相同的全限定名,所以爲了確保這些應用之間互不干擾,就需要由各應用的類加載器去加載所屬應用的類,這樣就不會發生類衝突了。

    上面說到了Launcher$ExtClassLoader和應用類加載器(Launcher$AppClassLoader)分別會用於加載哪些類,爲了對類加載器有一個完整的認識,下面再介紹啓動類加載器(Bootstrap Classloader)會從哪裏加載類。

    啓動類加載器(Bootstrap Classloader),是最先啓動的類加載器,默認是負責加載$JRE_HOME/lib目錄下面的類,也可以通過JVM參數-Xbootclasspath來指定需要加載類的路徑,不過虛擬機爲了安全性以及功能的完整性,並不是任何存在於啓動類加載器路徑下的jar都會被加載,它是通過jar的名字來區分需要加載的類的,如rt.jar等,其它的類即使放在啓動類加載器的加載目錄下,也是不會被加載的,有興趣的話可以自己編譯一個jar放到$SRE_HOME/lib目錄下,然後通過加上JVM參數-XX:+TraceClassLoading任意運行一個java程序,看其中是否有你jar,結論是不會存在的。 

    前面介紹了啓動類加載器、擴展類加載器以及應用類加載器,現在回到正題,實現一個自定義的類加載器,我們可以參考擴展類加載器及應用類加載器,可分別通過重寫ClassLoader中的loadClass方法或者findClass方法,但是重寫不同的方法,就像擴展類加載器以及應用類加載器一樣,要達到的目的是不一樣的。

 

    重寫loaderClass方法

    如果要想在JVM的不同類加載器中保留具有相同全限定名的類,那就要通過重寫loadClass來實現,此時首先是通過用戶自定義的類加載器來判斷該類是否可加載,如果可以加載就由自定義的類加載器進行加載,如果不能夠加載才交給父類加載器去加載。

    這種情況下,就有可能有大量相同的類,被不同的自定義類加載器加載到JVM中,並且這種實現方式是不符合雙親委派模型。但是不能夠說這種實現方式就一定是錯誤的,有可能當前的場景就需要這樣的方式,如容器插件應用場景就適合。

    一個插件容器,如下圖所示:

 

    要允許不同的插件增加到容器中,就需要採用這種方式,因爲我們沒有辦法保證不同的插件中不能夠有相同全限定名的類存在,如A插件中存在了test.Test這麼一個類,B插件中也可能會有這麼一個相同的類,不能說A插件的test.Test類被加載了,B插件的test.Test就不可再加載了,這就會導致B或/和A插件工作不正常,因爲這兩個不同的類實現的功能可能完全不同,或者可以說他們之間是一點關係都沒有。

    但是如果實現自定義類的場景不是類似上面的插件容器場景,最好還是實現findClass,這個也是Sun推薦的實現方式,並且它是符合雙親委派模型的。下面是一個自定義類加載器的實現源碼:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. ClassLoader myloader = new ClassLoader() {  
  2.   
  3.     @Override  
  4.   
  5. public Class<?> loadClass(String name)   
  6.   
  7.                              throws ClassNotFoundException {  
  8.   
  9.         try {  
  10.   
  11.             // 這個getClassInputStream根據情況實現  
  12.   
  13.             InputStream is = getClassInputStream(name);  
  14.   
  15.             if (is == null) {  
  16.   
  17.                 return super.loadClass(name);  
  18.   
  19.             }  
  20.   
  21.             byte[] bt = new byte[is.available()];  
  22.   
  23.             is.read(bt);  
  24.   
  25.             return defineClass(name, bt, 0, bt.length);  
  26.   
  27.         } catch (IOException e) {  
  28.   
  29.             throw new   
  30.   
  31.               ClassNotFoundException("Class " + name + " not found.");  
  32.   
  33.         }  
  34.   
  35.     }  
  36.   
  37. }  

    注:上面源碼中的getClassInputStream(name)方法根據實際情況去實現了,如果要加載的類是在類路徑下,這個方法的實現可能是這樣的:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. String filename = name.replace('.''/')+".class";  
  2.   
  3. InputStream is = getClass().getResourceAsStream(filename);  

    如果要加載的類不是在類路徑下,那可能要通過獲取文件流的方式進行加載了,加載的的實現可能是這樣的了:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. InputStream is = new FileInputStream(new File(name));  
    也有可能是通過網絡獲取的,只要能夠獲取得到就行。

 

    重寫findClass方法

    重寫findClass方法的自定義類,首先會通過父類加載器進行加載,如果所有父類加載器都無法加載,再通過用戶自定義的findClass方法進行加載。如果父類加載器可以加載這個類或者當前類已經存在於某個父類的容器中了,這個類是不會再次被加載的,此時用戶自定義的findClass方法就不會被執行了。

    重寫findClass方法是符合雙親委派模式的,它保證了相同全限定名的類是不會被重複加載到JVM中,下面是JDK 6u33中ClassLoader的loadClass方法:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {  
  2.   
  3.         // First, check if the class has already been loaded  
  4.   
  5.         Class c = findLoadedClass(name);  
  6.   
  7.         if (c == null) {  
  8.   
  9.             try {  
  10.   
  11.                 if (parent != null) {  
  12.   
  13.                     c = parent.loadClass(name, false);  
  14.   
  15.                 } else {  
  16.   
  17.                     c = findBootstrapClassOrNull(name);  
  18.   
  19.                 }  
  20.   
  21.             } catch (ClassNotFoundException e) {  
  22.   
  23.                 // ClassNotFoundException thrown if class not found  
  24.   
  25.                 // from the non-null parent class loader  
  26.   
  27.             }  
  28.   
  29.             if (c == null) {  
  30.   
  31.                 // If still not found, then invoke findClass in order  
  32.   
  33.                 // to find the class.  
  34.   
  35.                 c = findClass(name);  
  36.   
  37.             }  
  38.   
  39.         }  
  40.   
  41.         if (resolve) {  
  42.   
  43.             resolveClass(c);  
  44.   
  45.         }  
  46.   
  47.         return c;  
  48.   
  49.     }  
    我們可以看到其中被標紅放大findClass方法,是被悲慘的放在了Catch塊中的,不出問題是不需要它出來救火的。而ClassLoaderfindClass方法的默認實現是直接拋出ClassNotFoundException異常的,如下代碼:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. protected Class<?> findClass(String name) throws ClassNotFoundException {  
  2.   
  3. throw new ClassNotFoundException(name);  
  4.   
  5.     }  
    如果用戶沒有實現自定義的findClass方法,那只要是執行到了findClass,還是直接拋出異常,那這個方法看起來怪怪的,它有什麼目的呢?

    這個是有一個歷史原因的,因爲雙親委派模型是JDK1.2以後才引用進來的,在1.1及以前用戶實現自己的類加載器都是通過重寫loadClass方法實現,爲了兼容原來的實現方式,就選擇了增加findClass這麼一種妥協的方式。

    實現自定義類,源碼可以複用上面重寫loadClass的實現,只需要將loadClass方法爲findClass方法即可。

 

本文轉自:http://blog.csdn.net/fenglibing/article/details/17471659

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章