Java高新技術5(類加載器(ClassLoader))


 

1.類加載器概述:html

1.Java虛擬機中能夠安裝多個類加載器,系統默認三個主要類加載器,java

每一個類負責加載特定位置的類:BootStrap,ExtClassLoader,AppClassLoader設計模式

2.類加載器也是Java類,由於其餘是java類的類加載器自己也要被類加載器加載ide

    顯然必須有第一個類加載器不是否是java類,這正是BootStrap。測試

2.類加載器測試:this

示意圖:加密

類加載器樹狀圖

Java虛擬機中的全部類裝載器採用具備父子關係的樹形結構進行組織,在實例化每一個類裝載器對象時,須要爲其指定一個父級類裝載器對象或者默認採用系統類裝載器爲其父級類加載。 spa

package com.itheima.day5;

import java.util.Map;

/*
 系統默認的三個主要類加載器:
 BootStrap ClassLoader:引導類加載器/啓動類加載器
 ExtClassLoader(Extension Class Loader):擴展類加載器
 AppClassLoader:系統類加載器
 */

public class ClassLoaderDemo1 {

    /**
     * @param args
     */
    public static void main(String[] args)throws Exception {
        // TODO Auto-generated method stub
       //獲取當前類的加載器
        System.out
            .println(ClassLoaderDemo1.class.getClassLoader().getClass().getName());
          System.out
            .println(System.class.getClassLoader()+"\n");//返回null,System類是由BootStrap(引導類加載器)加載
                                                        //該加載器採用C++編寫,是在JVM開始運行的時候加載java的核心類
                                                       //其自己不屬於Java任何類,所以返回null
    
    //三個類加載器查找的路徑
    System.out.println(System.getProperty("sun.boot.class.path")+"\n");//BootStrap加載該屬性值指定的目錄下.class(有包的話:package/.class或指定的.jar
                                                                     
    System.out.println(System.getProperty("java.ext.dirs")+"\n");//ExtClassLoader加載該屬性指定的目錄下的 全部.jar(該屬性只表明了ext所在的目錄)
                                                    
    System.out.println(System.getProperty("java.class.path")+"\n");//AppClassLoader加載該屬性指定的目錄下.class(有包的話:package/.class)或指定的.jar
    
    
//三個類加載器之間的關係
    ClassLoader classLoader=ClassLoaderDemo1.class.getClassLoader();
    while(classLoader!=null){
    System.out.println(classLoader.getClass().getName());
       classLoader=classLoader.getParent();//返回其委託的父類加載器
    }
    System.out.println(classLoader+"\n");
    


    
      /*for(Map.Entry<Object,Object> me : System.getProperties().entrySet()){
         System.out.println(me.getKey()+"....."+me.getValue());
    }  
    */
    
   }                                                  
}

類加載器演示

3.類加載器的委託機制:線程

類加載的委託機制:
當Java虛擬機要加載一個類時,到底派出哪一個類加載器去加載呢?
1.首先當前線程的類加載器去加載線程中的第一個類。
2.若是類A中引用了類B(繼承,實現,使用),Java虛擬機將使用加載類A的類裝載器來加載類B。
3.還能夠直接調用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類。設計

每一個類加載器加載類時,又先委託給其上級類加載器。
當全部祖宗類加載器沒有加載到類,回到發起者類加載器,還加載不了,則拋ClassNotFoundException,
不是再去找發起者類加載器的兒子,由於沒有getChild方法,即便有,那有多個兒子,找哪個呢?

舉例:

例如:發起者類加載器:AppClassLoader
在AppClassLoader加載該類前會委託給其父類加載器 ExtClassLoader,而ExtClassLoader還有父類加載器
在委託給BootStrap,BootStrap沒有父類加載器->此時BootStrap開始在sun.boot.class.path指定的.jar中查找該類
                                             ->找到則加載該類
                                             ->沒找到->讓其直接子類加載器ExtClassLoader在java.ext.dirs的.jar中找->找到則加載該類
                                                                                                                                      ->沒找到->

                                              再讓其直接子類加載器AppClassLoader在java.class.path下中找->找到則加載該類
                                                                                                                           ->沒找到ClassNotFoundException

委託機制優勢:

類加載使用委託機制的優勢:
1.便於集中管理
2.若是父類類加載已加載->直接拿過來用
    若是有兩個類加載器:
    A和B,沒有委託機制
   若是兩個自定義類分別指定了A和B加載,這兩個類都要用到C.class
   A加載C.class,B依然還要加載C.class->JVM中字節碼不惟一
  委託機制保證了Jvm中相同的字解碼只有一份

 

4.自定義類加載器與類的簡單解密/加密:

ClassLoader幾個重要方法:

/*
自定義類加載器 繼承ClassLoader,須要複寫findClass方法
  protected Class<?> loadClass(String name,
                             boolean resolve)
                      throws ClassNotFoundException使用指定的二進制名稱來加載類。此方法的默認實現將按如下順序搜索類: 

1.調用 findLoadedClass(String) 來檢查是否已經加載類。

2.在父類加載器上調用 loadClass 方法。若是父類加載器爲 null,則使用虛擬機的內置類加載器(委託機制) 

3.調用 findClass(String) 方法查找類。
-->首先全部的父類加載器在本身相應的路徑下查找
-->在調用findClass去找
不一樣的自定義加載器查找方式不一樣,但其委託的父類的加載器查找方式相同->所以繼承ClassLoader複寫findClass 以上是模板方法設計模式 
  
*/
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);//在遞歸loadClass                     } else {
                        c = findBootstrapClassOrNull(name);//再去委託BootStrap
                    }
                } 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();
                    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;
        }
    }

工程目錄

package com.itheima.day5;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;
public class CustomClassLoader extends ClassLoader{

    /**
     * @param args;
     */
//args[0]: bin\com\itheima\day5\EncryptClass.class
//args[1]:myEncryptClass
    public static void main(String[] args)throws Exception {
        // TODO Auto-generated method stub
        //args[0]做爲源路徑,args[1]做爲目的路徑
        //我把加密和解密在同一個main中執行,在運行加密後註釋掉 防止在刪除bin下的EncryptClass後,因爲找不到會報
        //FileNotFoundException
      /* File file=new File(args[0]);
        FileInputStream fis=new FileInputStream(file);
        FileOutputStream fos=new FileOutputStream(args[1]+\\+file.getName());
        encryptClass(fis,fos);
        fis.close();
        fos.close();*/
        
        //有名包下的類訪問無名包下的類
       //ClassLoader.getSystemClassLoader().loadClass("Test").newInstance();
       
      Class cls=new CustomClassLoader("MyEncryptClass").loadClass("EncryptClass");
       System.out.println(cls.newInstance().toString());//success
       
    }
    
    
    //簡單加密
    public static void encryptClass(InputStream in,OutputStream os)throws IOException{
        int aByte=0;
        while((aByte=in.read())!=-1){
               os.write(aByte^0xff);//最簡單的加密->.class中的每一個字節^255
        }
    }
    
    
    private String  classDir;
    
    public CustomClassLoader(String classDir) {
        super();
        this.classDir = classDir;
    }

    @Override
    public Class<?> findClass(String name)throws ClassNotFoundException{
        String path=classDir+"\\"+name+".class";
        File file=new File(path);
        FileInputStream fis=null;
        byte[] byteArr=new byte[1024];
        int aByte=0;
        int pointer=0;
        try{
            fis=new FileInputStream(file.getAbsolutePath());
           
            while((aByte=fis.read())!=-1)
                 byteArr[pointer++]=(byte)(aByte ^ 255);//解密
         }
        catch(Exception e){
          e.printStackTrace();
        }
        finally{
          if(fis!=null)    
              try{
                  fis.close();
              }
             catch(Exception e){
                 e.printStackTrace();
             }
        }
      return defineClass(null,byteArr,0,pointer);
    }
    
}

defineClass方法解析:

/*
defineClass方法解析:
EncryptClass.java是在com/itheima/day5包下建立的->那麼在其.class中包含有包名信息->在defineClass(name,byteArr,0,pointer);->defineClass會以byteArr中的字節數據進行解析->若是我傳的name和它解析的不一致->NoClassDefFoundError
這時能夠把name換成com.itheima.day5.EncryptClass(全限定名)測試一下,OK.
而傳入null也能夠成功意思就是說:"哥們我不知道這個完整類名,你根據.class文件中的字節解析一下吧,這時候不在比對你指定的name"
*/

5.ClassNotFoundException與NoClassDefFoundError

摘自:http://www.ibm.com/developerworks/cn/java/j-dclp2.html

1. 

ClassNotFoundException 是最多見的類裝入異常類型。它發生在裝入階段。Java 規範對 ClassNotFoundException 的描述是這樣的:

當應用程序試圖經過類的字符串名稱,使用如下三種方法裝入類,但卻找不到指定名稱的類定義時拋出該異常。

  • Class 中的 forName() 方法。
  • ClassLoader 中的 findSystemClass() 方法。
  • ClassLoader 中的 loadClass() 方法。

因此,若是顯式地裝入類的嘗試失敗,那麼就拋出 ClassNotFoundException。清單 1 中的測試用例提供的示例代碼拋出了一個 ClassNotFoundException

發起者類加載器及其全部父類加載器均找不到->ClassNotFoundException

2.

當類 A 擴展(使用)了類 B,當類 A 裝入時,類裝入器會隱式地裝入類 B。若是類 B 不存在,因此拋出 NoClassDefFoundError

另外就是我上面的defineClass(String,byte[],int,int):其解析的類名與name不一致->NoClassDefFoundError

相關文章
相關標籤/搜索