java 詳解類加載器的雙親委派及打破雙親委派


java 詳解類加載器的雙親委派及打破雙親委派

https://blog.csdn.net/chang_ge/article/details/80262115

 

/www.jb51.net/article/102920.htm

https://www.cnblogs.com/wxd0108/p/6681618.html

 

其實,雙親委派模型並不復雜。自定義類加載器也不難!隨便從網上搜一下就能搜出一大把結果,然后copy一下就能用。但是,如果每次想自定義類加載器就必須搜一遍別人的文章,然后復制,這樣顯然不行。可是自定義類加載器又不經常用,時間久了容易忘記。相信你經常會記不太清loadClassfindClassdefineClass這些函數我到底應該重寫哪一個?它們主要是做什么的?本文大致分析了各個函數的流程,目的就是讓你看完之后,難以忘記!或者說,延長你對自定義類加載器的記憶時間!隨時隨地想自定義就自定義!

1. 雙親委派模型

關於雙親委派模型,網上的資料有很多。我這里只簡單的描述一下,就當是復習。

1.1 什么是雙親委派模型?

首先,先要知道什么是類加載器。簡單說,類加載器就是根據指定全限定名稱將class文件加載到JVM內存,轉為Class對象。如果站在JVM的角度來看,只存在兩種類加載器:

  • 啟動類加載器(Bootstrap ClassLoader):由C++語言實現(針對HotSpot),負責將存放在<JAVA_HOME>\lib目錄或-Xbootclasspath參數指定的路徑中的類庫加載到內存中。

  • 其他類加載器:由Java語言實現,繼承自抽象類ClassLoader。如:

    • 擴展類加載器(Extension ClassLoader):負責加載<JAVA_HOME>\lib\ext目錄或java.ext.dirs系統變量指定的路徑中的所有類庫。
    • 應用程序類加載器(Application ClassLoader)。負責加載用戶類路徑(classpath)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器默認就是用這個加載器。

雙親委派模型工作過程是:如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每個類加載器都是如此,只有當父加載器在自己的搜索范圍內找不到指定的類時(即ClassNotFoundException),子加載器才會嘗試自己去加載。


類加載器的雙親委派模型

1.2 為什么需要雙親委派模型?

為什么需要雙親委派模型呢?假設沒有雙親委派模型,試想一個場景:

黑客自定義一個java.lang.String類,該String類具有系統的String類一樣的功能,只是在某個函數稍作修改。比如equals函數,這個函數經常使用,如果在這這個函數中,黑客加入一些“病毒代碼”。並且通過自定義類加載器加入到JVM中。此時,如果沒有雙親委派模型,那么JVM就可能誤以為黑客自定義的java.lang.String類是系統的String類,導致“病毒代碼”被執行。

而有了雙親委派模型,黑客自定義的java.lang.String類永遠都不會被加載進內存。因為首先是最頂端的類加載器加載系統的java.lang.String類,最終自定義的類加載器無法加載java.lang.String類。

或許你會想,我在自定義的類加載器里面強制加載自定義的java.lang.String類,不去通過調用父加載器不就好了嗎?確實,這樣是可行。但是,在JVM中,判斷一個對象是否是某個類型時,如果該對象的實際類型與待比較的類型的類加載器不同,那么會返回false。

舉個簡單例子:

ClassLoader1ClassLoader2都加載java.lang.String類,對應Class1、Class2對象。那么Class1對象不屬於ClassLoad2對象加載的java.lang.String類型。

二、打破雙親委派機制則不僅要繼承ClassLoader類,還要重寫loadClass和findClass方法,如下例子:

①定義Test類。

?
1
2
3
4
5
public  class  Test {
   public  Test(){
     System.out.println( this .getClass().getClassLoader().toString());
   }
}

②重新定義一個繼承ClassLoader的TestClassLoaderN類,這個類與前面的TestClassLoader類很相似,但它除了重寫findClass方法外還重寫了loadClass方法,默認的loadClass方法是實現了雙親委派機制的邏輯,即會先讓父類加載器加載,當無法加載時才由自己加載。這里為了破壞雙親委派機制必須重寫loadClass方法,即這里先嘗試交由System類加載器加載,加載失敗才會由自己加載。它並沒有優先交給父類加載器,這就打破了雙親委派機制。

 

public  class  TestClassLoaderN  extends  ClassLoader {
 
   private  String name;
 
   public  TestClassLoaderN(ClassLoader parent, String name) {
     super (parent);
     this .name = name;
   }
 
   @Override
   public  String toString() {
     return  this .name;
   }
 
   @Override
   public  Class<?> loadClass(String name)  throws  ClassNotFoundException {
     Class<?> clazz =  null ;
     ClassLoader system = getSystemClassLoader();
     try  {
       clazz = system.loadClass(name);
     catch  (Exception e) {
       // ignore
     }
     if  (clazz !=  null )
       return  clazz;
     clazz = findClass(name);
     return  clazz;
   }
 
   @Override
   public  Class<?> findClass(String name) {
 
     InputStream is =  null ;
     byte [] data =  null ;
     ByteArrayOutputStream baos =  new  ByteArrayOutputStream();
     try  {
       is =  new  FileInputStream( new  File( "d:/Test.class" ));
       int  c =  0 ;
       while  (- 1  != (c = is.read())) {
         baos.write(c);
       }
       data = baos.toByteArray();
     catch  (Exception e) {
       e.printStackTrace();
     finally  {
       try  {
         is.close();
         baos.close();
       catch  (IOException e) {
         e.printStackTrace();
       }
     }
     return  this .defineClass(name, data,  0 , data.length);
   }
 
   public  static  void  main(String[] args) {
     TestClassLoaderN loader =  new  TestClassLoaderN(
         TestClassLoaderN. class .getClassLoader(),  "TestLoaderN" );
     Class clazz;
     try  {
       clazz = loader.loadClass( "test.classloader.Test" );
       Object object = clazz.newInstance();
     catch  (Exception e) {
       e.printStackTrace();
     }
   }
 
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM