国产成人精品18p,天天干成人网,无码专区狠狠躁天天躁,美女脱精光隐私扒开免费观看

java中的Class裝載系統ClassLoader是怎樣使用

發(fā)布時(shí)間:2021-09-27 17:50 來(lái)源:億速云 閱讀:0 作者:柒染 欄目: 開(kāi)發(fā)技術(shù)

java中的Class裝載系統ClassLoader是怎樣使用,針對這個(gè)問(wèn)題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。

ClassLoader在Java中有著(zhù)非常重要的作用,它主要工作是在Class裝載的加載階段,主要作用是從系統外部獲得Class二進(jìn)制數據。

1. 認識ClassLoader

ClassLoader是java的核心組件,所有的Class都是由ClassLoader進(jìn)行加載的,ClassLoader負責通過(guò)各種方式將ClassLoader在整個(gè)裝載階段,只能影響類(lèi)的加載,而無(wú)法通過(guò)ClassLoader改變類(lèi)的連接和初始化行為。

從代碼層面上看,ClassLoader是一個(gè)抽象類(lèi),它提供了一些重要的接口,用于自定義Class的加載流程和加載方式。ClassLoader的主要方法如下:

  • public Class<?> loadClass(String name) throws ClassNotFoundException:給定一個(gè)類(lèi)名,加載一個(gè)類(lèi),返回代碼這個(gè)類(lèi)的Class實(shí)例,如果找不到類(lèi),則返回ClassNotFoundException異常。

  • protected final Class<?> defineClass(byte[] b, int off, int len):根據給定的字節碼流b定義一個(gè)類(lèi),offlen參數表示實(shí)際Class信息在byte數組中的位置和長(cháng)度,其中byte數組b是ClassLoader從外部獲取的。這是一個(gè)受保護的方法,只有在自定義ClassLoader子類(lèi)中可以使用。

  • protected Class<?> findClass(String name) throws ClassNotFoundException:查找一個(gè)類(lèi),這是一個(gè)受保護的方法,也是重載ClassLoader時(shí)重要的系統擴展點(diǎn)。這個(gè)方法在loadClass()中被調用,用于自定義查找類(lèi)的邏輯。如果不需要修改類(lèi)加載默認機制,只是想改變類(lèi)加載的形式,就可以重載該方法。

  • protected final Class<?> findLoadedClass(String name):這也是一個(gè)受保護的方法,它會(huì )尋找已經(jīng)加載的類(lèi)。這個(gè)方法是final方法,無(wú)法被修改。

ClassLoader的結構中,還有一個(gè)重要的字段:parnet。它也是一個(gè)ClassLoader的實(shí)例,這個(gè)字段所表示的ClassLoader稱(chēng)為這個(gè)ClassLoader的雙親。在類(lèi)加載的過(guò)程中,ClassLoader可能會(huì )將某些請求交給自己的雙親處理。

2. ClassLoader的分類(lèi)

在標準的java程序中,java虛擬機會(huì )創(chuàng )建3類(lèi)ClassLoader為整個(gè)應用程序服務(wù)。它們分別是:Bootstrap ClassLoader(啟動(dòng)類(lèi)加載器)、Extension ClassLoader(擴展類(lèi)加載器)和 App ClassLoader(應用類(lèi)加載器,也稱(chēng)系統類(lèi)加載器)。此外每一個(gè)應用程序還可以擁有自定義的 ClassLoader,以擴展java虛擬機獲取Class數據的能力。

ClassLoader層次結構如下圖所示。當系統需要使用一個(gè)類(lèi)時(shí),在判斷類(lèi)是否已經(jīng)被加載時(shí),會(huì )從底層類(lèi)加載器開(kāi)始進(jìn)行判斷。當系統需要加載一個(gè)類(lèi)時(shí),會(huì )從頂層類(lèi)開(kāi)始加載,依次向下嘗試,直到成功。

  • 啟動(dòng)類(lèi)加載器:完全由c語(yǔ)言實(shí)現,并且java中沒(méi)有對象與之對應,負責加載系統的核心類(lèi),比如rt.jar中的java類(lèi)。

  • 擴展類(lèi)加載器:用于加載%JAVA_HOME%/lib/ext/*.jar中的java類(lèi)。

  • 應用類(lèi)加載器:用于加載用戶(hù)類(lèi),也就是用戶(hù)程序的類(lèi)。

  • 自定義類(lèi)加載器:用于加載一些特殊途徑的類(lèi),一般也是用戶(hù)程序的類(lèi)。

下列代碼輸出了加載的類(lèi)加載器:

public class Demo04 {
    public static void main(String[] args) {
        ClassLoader cl = Demo04.class.getClassLoader();
        while (cl != null) {
            System.out.println(cl.getClass().getName());
            cl = cl.getParent();
        }
    }
}

代碼中先取得裝載當前類(lèi)Demo04ClassLoader,然后打印當前ClassLoader并獲得其雙親,直到類(lèi)加載器樹(shù)被遍歷完成。運行結果如下:

sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader

由此得知,Demo03是由AppClasLoader(應用類(lèi)加載器)加載的,而AppClassLoader的雙親為ExtClassLoader(擴展類(lèi)加載器)。從ExtClassLoader無(wú)法再取得啟動(dòng)類(lèi)加載器,因為這是一個(gè)系統級的純C語(yǔ)言實(shí)現。因此,任何啟動(dòng)類(lèi)加載器中加載的類(lèi)是無(wú)法獲得其ClassLoader實(shí)例的,比如:

String.class.getClassLoader()

由于String屬于java核心類(lèi),會(huì )被啟動(dòng)類(lèi)加載器加載,故以上代碼返回的是null.

3. ClassLoader 的雙親委托模式

系統中的ClassLoader在協(xié)同工作時(shí),默認會(huì )使用雙親委托模式。在類(lèi)加載的時(shí)候,系統會(huì )判斷當前類(lèi)是否已經(jīng)被加載,如果已經(jīng)被加載,就會(huì )直接返回可用的類(lèi),否則就會(huì )嘗試加載。在嘗試加載時(shí),會(huì )請求雙親處理,如果請求失敗,則會(huì )自己加載。

以下代碼顯示了ClassLoader加載類(lèi)的詳細過(guò)程,它在ClassLoader.loadClass()中實(shí)現:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 檢查類(lèi)是否已經(jīng)加載
            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) {
                    // 如果雙親不為null
                    // 在雙親加載不成功時(shí),拋出ClassNotFoundException
                }

                if (c == null) {
                    // 如果雙親加載不成功
                    // 使用findClass查找類(lèi)
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 定義類(lèi)加載器,記錄數據
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

判斷類(lèi)是否加載時(shí),應用類(lèi)加載器會(huì )順著(zhù)雙親路徑往上判斷,直到啟動(dòng)類(lèi)加載器。但是啟動(dòng)類(lèi)加載器不會(huì )往下詢(xún)問(wèn),這個(gè)委托是單向的。

4. 雙親委托模式的弊端

由前面的分析可知,檢查類(lèi)是否已加載的委托過(guò)程是單向。這種方式雖然從結構上比較清晰,使用各個(gè)ClassLoader的職責非常明確,但是會(huì )帶來(lái)一個(gè)問(wèn)題:即上層的ClassLoader無(wú)法訪(fǎng)問(wèn)下層的ClassLoader所加載的類(lèi),如下圖:

通常情況下,啟動(dòng)類(lèi)加載器中的類(lèi)為系統核心類(lèi),包括一些重要的系統接口,而在應用類(lèi)加載器中為應用類(lèi)。按照這種模式,應用類(lèi)訪(fǎng)問(wèn)系統類(lèi)自然沒(méi)問(wèn)題,但是系統類(lèi)訪(fǎng)問(wèn)應用類(lèi)就會(huì )出現問(wèn)題。比如,在系統類(lèi)中提供了一個(gè)接口,該接口需要在應用中得以實(shí)現,還綁定一個(gè)工廠(chǎng)方法,用于創(chuàng )建該接口的實(shí)例,而接口和工廠(chǎng)方法都在啟動(dòng)類(lèi)加載器中。這些就會(huì )出現該工廠(chǎng)方法無(wú)法創(chuàng )建由應用類(lèi)加載器的應用實(shí)例的問(wèn)題。

5. 雙親委托模式的補充

在java平臺中,通常把核心類(lèi)(rt.jar)中提供外部服務(wù)、可由應用層自行實(shí)現的接口稱(chēng)為Service Provider Interface,即SPI.

下面以javax.xml.parsers中實(shí)現XML文件解析功能模塊為例,說(shuō)明如何在啟動(dòng)類(lèi)加載中訪(fǎng)問(wèn)由應用類(lèi)加載器實(shí)現的SPI接口實(shí)例。

public static DocumentBuilderFactory newInstance() {
    return FactoryFinder.find(
            /* The default property name according to the JAXP spec */
            DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
            /* The fallback implementation class name */
            "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
}

FactoryFinder.find()函數試圖加載并返回一個(gè)DocumentBuilderFactory實(shí)例。當這個(gè)實(shí)例在應用層jar包時(shí),它會(huì )使用如下方法進(jìn)行查找:

Object provider = findJarServiceProvider(factoryId);

其中factoryId就是字條串javax.xml.parsers.DocumentBuilderFactory,findJarServiceProvider的主要內容如下代碼所示(這段代碼并非jdK中的源碼,為了展示主要功能,做了刪減):

private static Object findJarServiceProvider(String factoryId) throw ConfigurationError {
	String serviceId = "META-INF/services" + factoryId;
	InputStream is = null;
	ClassLoader cl = ss.getContextClassLoader();
	InputStream is = ss.getResourceAsStream(cl, serviceId);
	BufferedReader rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
	String factoryClassName = rd.readLine();
	return newInterface(factoryClassName, cl, false, useBSClsLoader);
}

從以上代碼可知,系統通過(guò)讀取jar包中META-INF/services目錄下的類(lèi)名文件讀取工廠(chǎng)類(lèi)類(lèi)名,然后根據類(lèi)名生成對應的實(shí)例,并將此ClassLoader傳入newInstance()方法,由這個(gè)ClassLoader完成實(shí)例的加載和創(chuàng )建,而不是由這段代碼所在的啟動(dòng)類(lèi)加載品加載。從而解決了啟動(dòng)類(lèi)加載器無(wú)法訪(fǎng)問(wèn)factoryClassName指定類(lèi)的問(wèn)題。

以上代碼中,加載工廠(chǎng)類(lèi)方法略有曲折,我們平時(shí)寫(xiě)代碼時(shí),知道了一個(gè)類(lèi)的包名.類(lèi)名,要生成該類(lèi)的對象,通常是這么進(jìn)行的:

  1. Class.forname("包名.類(lèi)名"),拿到Class對象。

  2. 拿到Class對象后,調用Class.newInstance()方法,生成該對象的實(shí)例。

但是,在DocumentBuilderFactory中,這樣做就行不通了,主要原因在于Class.forName()無(wú)法拿到類(lèi)加載器。我們來(lái)看看Class.forName()的源碼:

public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

從上面可以看到,在哪個(gè)類(lèi)里調用了Class.forName(),就使用加載那個(gè)類(lèi)的類(lèi)加載器進(jìn)行類(lèi)加載,即DocumentBuilderFactory調用了Class.forName(),就使用加載DocumentBuilderFactory的類(lèi)加載器進(jìn)行加載包名.類(lèi)名,但問(wèn)題是DocumentBuilderFactory是由BootClassLoader加載的,獲取到的類(lèi)加載器是null,這是無(wú)法加載包名.類(lèi)名。

6. 突破雙親模式

雙親模式的類(lèi)加載方式是虛擬機默認的行為,但并非必須這么做,通過(guò)重載ClassLoader可以修改該行為。下面將演示如何打破默認的雙親模式:

package jvm.chapter10;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;


class MyClassLoader extends ClassLoader {

    private String fileName;

    public MyClassLoader(String fileName) {
        this.fileName = fileName;
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class re = findClass(name);
        if (re != null) {
            return re;
        }
        System.out.println("load class " + name + " failed, parent load start");
        return super.loadClass(name, resolve);
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        Class clazz = this.findLoadedClass(className);
        if (null == clazz) {
            try {
                String classFile = getClassFile(className);
                FileInputStream fis = new FileInputStream(classFile);
                FileChannel fileChannel = fis.getChannel();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                WritableByteChannel outChannel = Channels.newChannel(baos);
                ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
                while (true) {
                    int i = fileChannel.read(buffer);
                    if (i == 0 || i == -1) {
                        break;
                    }
                    buffer.flip();
                    outChannel.write(buffer);
                    buffer.clear();
                }
                fis.close();
                byte[] bytes = baos.toByteArray();
                clazz = defineClass(className, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return clazz;
    }

    private String getClassFile(String packageName) {
        return fileName + packageName.replaceAll("\\.", File.separator) + ".class";
    }

}

/**
 * {這里添加描述}
 *
 * @author chengyan
 * @date 2019-11-29 4:12 下午
 */
public class Demo05 {

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("/Users/chengyan/IdeaProjects/myproject/DataStructuresAndAlgorithms/out/production/DataStructuresAndAlgorithms/");
        Class clz = myClassLoader.loadClass("jvm.chapter10.Demo01");
        System.out.println(clz.getClassLoader().getClass().getName());

        System.out.println("=======class load tree===========");
        ClassLoader cl = clz.getClassLoader();
        while(cl != null) {
            System.out.println(cl.getClass().getName());
            cl = cl.getParent();
        }
    }

}

以上代碼通過(guò)自定義ClassLoader重載loadClass()方法,改變了默認的委托雙親加載的方式,運行結果如下:

java.io.FileNotFoundException: /Users/chengyan/IdeaProjects/myproject/DataStructuresAndAlgorithms/out/production/DataStructuresAndAlgorithms/java/lang/Object.class (No such file or directory)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at jvm.chapter10.MyClassLoader.findClass(Demo05.java:36)
	at jvm.chapter10.MyClassLoader.loadClass(Demo05.java:22)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at jvm.chapter10.MyClassLoader.findClass(Demo05.java:52)
	at jvm.chapter10.MyClassLoader.loadClass(Demo05.java:22)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at jvm.chapter10.Demo05.main(Demo05.java:76)
load class java.lang.Object failed, parent load start
jvm.chapter10.MyClassLoader
=======class load tree===========
jvm.chapter10.MyClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader

可以看到,程序首先試圖由MyClassLoader加載Object類(lèi),但由于指定的路徑中沒(méi)有該類(lèi)信息,故加載失敗,拋出異常,但隨后就由應用類(lèi)加載器加載成功。接著(zhù)嘗試加載Demo01,Demo01在指定的路徑中,加載成功。打印加載Demo01ClassLoader,顯示為MyClassLoader,打印ClassLoader層次,依次為MyClassLoader,AppClassLoader,ExtClassLoader.

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。

人妻AV无码中文专区久久| 国产午夜福利片在线观看| 亚洲色成人网站WWW永久男男| 国产乱子伦无套一区二区三区| 成年美女黄网站色大片不卡| 日韩成AV人片在线观看|