類加載器 ClassLoder詳解

1、類加載器定義html

 從做用的角度來認識類加載器:(根據API文檔)java

1.類加載器是一個負責加載類的對象,而ClassLoader是一個抽象類。類加載器將xxx.class文件加載到JVM,生成xxx的Class對象。(Class詳見反射 點擊這裏)web

2.類加載器被安全管理器用來表示安全範圍。數組

2、類加載器種類及工做原理安全

 主要有三 :服務器

1.Bootstrap Loader(引導加載器):負責加載%JAVA_HOME%/jre/lib下的核心jaride

2.Extended Loader(擴展加載器):負責加載%JAVA_HOME%/jre/lib/ext下的擴展jarthis

3.AppClass Loader(應用加載器):負責加載該應用程序CLASSPATH下的jar和.class文件spa

 工做原理:code

  每個類加載器都有一個與之關聯的上級類加載器,類加載器加載器類時使用委託機制。當須要加載一個類時,它首先將該任務委託給上級加載器,若是上級加載器沒能加載到,而後再自行加載,這是一個遞歸的過程。應用加載器的上級是擴展加載器,擴展加載器的上級是引導加載器,而引導加載無上級加載器。

3、類加載器的安全管理

  JVM爲每一個類加載器維護了一個名字空間,每一個名字空間只能加載一個同名的類,不一樣名字空間可加載同名的類。在JVM中,同一個名字空間下的類能夠直接交互,不一樣則不能夠。若是a加載器和b加載器都加載了A類,那麼a加載器下的類要訪問A類,那麼必定是訪問的a加載器下的A類。這樣,經過類加載器就能夠進行安全管理。

  舉個例子:咱們本身寫了一個類叫java.lang.String和類庫裏的String的全限定名如出一轍。如今在代碼中出現了String類,須要加載,過程以下。

    >首先這個任務是應用加載器的,根據委託機制,應用加載器將任務委託給上級擴展加載器。

      >擴展加載器接收到該任務,將任務委託給上級引導加載器。

        >引導加載器加載在覈心類庫中尋找java.lang.String,找到了,加載,生成Class對象。

        >引導加載器將結果返回給擴展加載器。

      >擴展加載器將結果返回給應用加載器。

    >應用加載器獲得結果。

  應用加載器的到的結果是引導加載器從核心類庫中加載的java.lang.String 並無加載到咱們本身寫的String。這樣就保證了安全性。java其餘不少類都依賴String 類,若是加載了咱們本身寫的String就會形成嚴重的問題。類加載器的委託機制就提供了很好的安全防禦。

  在例如:咱們寫了一個類叫java.lang.Hello,java核心類庫中並不存在這個類,但它的所屬的包結構倒是java.lang,而同一個包下的類具備訪問protected成員的權限,(protected詳細解析 點擊這裏)這樣就又會形成一些安全問題。分析一下類加載器加載Hello類的過程,按照上面的委託機制能夠肯定,引導、擴展加載器範圍內並無這個類,最後Hello是被應用加載器加載的,那麼這個類是在應用加載器的名字空間下。開頭已經給出,不一樣名字空間下的類不能直接交互,那麼此處的java.lang.Hello並不能像核心類庫的java.lang包下的類同樣擁有protected的訪問權限。類加載器的委託機制再一次提供了安全保護。

4、自定義類加載器

   經過反射加載一個不存在的類

@Test
    public void funxx() throws ClassNotFoundException {
        Class.forName("xxx");
    }

  異常棧:

  

  loaderClass源碼:

 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);
                    } else {
                        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();
                    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;
        }
    }

 

  分析以上信息,能夠發現,ClassLoader加載類是經過loadClass()方法完成的,其工做流程以下:

  1.調用findLoadedClass()查看該類是否被加載過,若是沒有,返回null

  2.若是findLoadedClass()返回null,那麼使用委託機制進行類加載。

  3.若是getParent().loadClass() != null , 代表加載成功,不然調用本類的findClass()方法進行加載。

  因而可知,咱們自定義一個類加載器,只須要繼承ClassLoader,並重寫其findClass()方法便可。

  下面是我的寫的一個ClassLoader

package cn.edu;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class CustomizedClassLoader extends ClassLoader{

    private String classpath;                                                
    
    public CustomizedClassLoader() {
        
    }
    
    public CustomizedClassLoader(String classpath) {
        this.classpath = classpath;
    }
    
    //當loaderClass沒有找到該類時,會自動調用此方法尋找類。
    @Override
    public  Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = this.getBytesData(name);                           //獲取.class文件的字節數組
            if(data == null) {
                throw new ClassNotFoundException("找不到類:" + name);
            }
            return this.defineClass(name, data, 0, data.length);             //loaderClass繼承的方法,該方法經過字節數組生成Class對象
        }catch(IOException e){
            throw new ClassNotFoundException("找不到類:" + name);             
        }
    }
    
    //讀取.class文件,返回字節數組,若讀取到的內容爲空,則返回null
    private byte[] getBytesData(String name) throws IOException{
        name = name.replace(".", "//") + ".class";                           //解析binary name,獲得文件絕對路徑
        File file = new File(classpath,name);                                
        byte[] res = this.readFile(file);
        if(res.length == 0) {                                                //字節數組爲空,返回null
            return null;
        }
        return res;
    }
    
    //讀取指定文件,返回字節數組,若找不到該文件,則IO異常
    private byte[] readFile(File file) throws IOException{
        FileReader reader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(reader);
        String str = null;
        String res = "";
        while( (str = bufferedReader.readLine()) != null) {
            res += str;
        }
        return res.getBytes();
    }
}

 

、Tomcat的類加載器

   Tomcat有兩類類加載器,一類是服務器類加載器,用於加載${CATALINA_HOME}/lib目錄下的jar,這些是web容器所依賴的jar。還有一類是應用類加載器,加載${CONTEXT_PATH}/WEB-INF下的lib和class目錄,用於加載該應用所依賴的jar和class。這兩種類加載器,不適用委託機制,在應用須要加載類時,首先由應用加載器加載,加載失敗再由服務器加載器加載,而且class目錄優先於lib目錄。而且每個應用都有一個對應的類加載器加載。因此在給項目導入jar時應該注意不能導入與服務器類庫有衝突的jar。

 

    本文我的編寫,水平有限,若有錯誤,懇請指出,歡迎討論分享。

相關文章
相關標籤/搜索