ClassLoader和類加載機(jī)制

1、背景

最近在做項目的過程中,由于系統(tǒng)需要提供一個對外接口,使系統(tǒng)使用者可以以腳本的形式提交自己的代碼,每個用戶可以在系統(tǒng)規(guī)范的約束下編寫腳本,由系統(tǒng)去執(zhí)行用戶的代碼,實現(xiàn)了熱部署。什么叫熱部署呢?簡單來說就是把代碼當(dāng)成U盤或者外設(shè)一樣即插即用,每個用戶可以維護(hù)自己的解決方案(也就是一段腳本,一個單獨的類),在更新修改解決方案的過程中而不需要重新編譯啟動整個系統(tǒng)。我們采用的方案就是GroovyClassLoader,我主要講一講自己對ClassLoader的理解和使用。

2、類加載與類加載器

類加載:

類加載的過程就是將Class文件中描述的各種信息加載到虛擬機(jī)中,供程序后期運(yùn)行和使用的。
類加載的生命周期主要分為五個步驟:

1、加載:

通過一個類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流
將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法去的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個代表這個類的java.lang.Class 對象,作為方法區(qū)的各種數(shù)據(jù)類型的入口

2、驗證

為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害到自身的安全。包括文件格式驗證,元數(shù)據(jù)驗證,字節(jié)碼驗證,符號引用驗證。

3、準(zhǔn)備

為變量分配內(nèi)存,設(shè)置類變量的初始值。

4、解析

將常量池中的符號應(yīng)用替代為直接引用。

5、初始化

是類加載生命周期的最后一個過程,執(zhí)行類中定義的java程序代碼

該過程如圖所示:


0_1319366219Na16.gif
0_1319366219Na16.gif

類加載器:

在前面的類加載過程中,大部分動作都是完全由虛擬機(jī)主導(dǎo)和控制的。而類加載器使得用戶可以在加載的過程中參與進(jìn)來,結(jié)合前面的內(nèi)容,類加載器就是將“通過一個類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流”這個動作放到j(luò)ava虛擬機(jī)外部來實現(xiàn)。將主動權(quán)交給程序猿。
類加載器和這個類本身確定了其在java虛擬機(jī)中的唯一性,每一個類加載器都有一個獨立的類命名空間,也就意味著,如果比較兩個類是否相等,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個Class文件,被同一個虛擬機(jī)加載,只要加載他們的類加載器不同,那么這兩個類就注定不相同。

java的類加載器:


0_1319366276S7Uf.gif
0_1319366276S7Uf.gif

1、Bootstrap Class Loader:負(fù)責(zé)加載JAVA_HOME/lib目錄下或-Xbootclasspath指定目錄的jar包;

2、Extention Class Loader:加載JAVA_HOME/lib/ext目錄下的或-Djava.ext.dirs指定目錄下的jar包。

3、System Class Loader:加載classpath或者-Djava.class.path指定目錄下的類或jar包。

ClassLoader各司其職,加載在不同路徑下的class文件,值得注意的是,類加載采用的是雙親委托的設(shè)計模式,即傳入一個類限定名,逐層向上到Bootstrap Class Loader中查找,如果找到即返回,若沒有找到,則在Extention Class Loader中找,若還沒有找到則在System Class Loader下找,即classpath中,如果還沒有找到,則調(diào)用findClass(name)方法,執(zhí)行用戶自己的類加載邏輯(可能在其他的地方)

ClassLoader中的幾個重要的方法:

1、loadClass(String name, boolean resolve):加載類的方法,在jdk1.2以前需要重寫該方法實現(xiàn)用戶自己的邏輯,1.2以后為了向下兼容,仍然可以重寫該方法,但是建議用戶將自己的加載邏輯實現(xiàn)在findName(name)中。這樣系統(tǒng)先向上尋找能否加載到該類,如果加在不到,將調(diào)用用戶自定義的findName函數(shù)加載對象.

   /**
     * @param name    類名字
     * @param resolve  是否解析,如果只是想知道該class是否存在可以設(shè)置該參數(shù)為false
     * @return 返回一個class泛型
     * @throws ClassNotFoundException
     */
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        /**
         * getClassLoadingLock(name)
         * 為類的加載操作返回一個鎖對象。為了向后兼容,這個方法這樣實現(xiàn):如果當(dāng)前的classloader對象注冊了并行能力,
         * 方法返回一個與指定的名字className相關(guān)聯(lián)的特定對象,否則,直接返回當(dāng)前的ClassLoader對象。
         */
        synchronized (getClassLoadingLock(name)) {
            // 首先查看class是否已經(jīng)被加載過
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //如果父加載器不為空,則委托給父加載器去加載
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        /**
                         *  如果父加載器為空,說明父加載器已經(jīng)是Bootstrap ClassLoader了,則直接使用根加載器加載,也就是使用虛擬機(jī)加
                         *  載器加載
                         */
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                //如果以上的加載器在自己的路徑上面都沒有加載到,則調(diào)用findClass(name)調(diào)用用戶自定義的加載器
                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();
                }
            }
            //根據(jù)resolve參數(shù)決定是否解析該類
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

2、ClassLoader getParent() :可以返回委托的父類加載器。在你自定義加載器找不到相應(yīng)類的時候,可以調(diào)用此方法,不過在ClassLoader的默認(rèn)實現(xiàn)中,ClassLoader先判斷父類加載器是否可以加載,然后再調(diào)用用戶自定義的findClass方法。

3、 resolveClass():若resolve參數(shù)為true的時候,我們需要調(diào)用該函數(shù),resolve我們的classLoader。

4、ClassLoader getSystemClassLoader():提供了一個直接訪問系統(tǒng)classloader的方法。

3、廢話少說上代碼!

下面我將以一個例子來闡述如何使用ClassLoader,自定義的ClassLoader將加載被加密的類,而且這個類存儲的路徑不在ClassPath中,也不可以被Bootstrap Class Loader和Extention Class Loader加載,在實際應(yīng)用中,可以是網(wǎng)絡(luò)中傳遞過來的加密字節(jié)流,抑或著是實現(xiàn)腳本的熱部署操作。

package com.siyu;


import java.io.*;

public class ClassLoaderTest extends ClassLoader {
    //自定義加載器加載該路徑下面的文件
    private String directory;

    public ClassLoaderTest(String directory) {
        this.directory = directory;
    }

    /**
     * 重寫findClass,用戶可以做以下的事情
     * 1.可以加載boot、ext、system加載器所加載不了的路徑下的文件
     * 2.可以解密加密后的class文件
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //解密密鑰
        byte key = (byte) 1;
        //加密文件的路徑
        String fileName = directory + name + ".class";
        File file = new File(fileName);
        byte[] decryptedByte = readFromFile(file);
        //解密為原始的class文件
        for (int i = 0; i < decryptedByte.length; i++) {
            decryptedByte[i] = (byte) (decryptedByte[i] ^ key);
        }
        //defineClass實現(xiàn)了鏈接階段的驗證等
        return defineClass(null, decryptedByte, 0, decryptedByte.length);
    }


    private byte[] readFromFile(File fileName) {
        try {
            byte[] bytes = null;
            FileInputStream fin = new FileInputStream(fileName);

            int i;
            if ((i = fin.read()) != -1) {
                //初始化數(shù)組大小和文件大小一樣
                bytes = new byte[fin.available()];
                fin.read(bytes);
            }
            return bytes;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private byte[] encrypt(byte[] bytes) {
        byte key = (byte) 1;
        //依次加密的代碼
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) (bytes[i] ^ key); //利用異或加密
        }
        return bytes;
    }

    public void encryptFile(String fileName, String directory) {
        try {
            String name = fileName.substring(fileName.lastIndexOf("\\") + 1, fileName.length() - 6);
            //加密文件的路徑
            String destFileName = directory + "encryted" + name + ".class";
            //如果加密文件不存在則創(chuàng)建加密文件
            File f = new File(destFileName);
            if (f == null) {
                f.createNewFile();
            }
            //加密
            byte[] encryptedByte = encrypt(readFromFile(new File(fileName)));
            FileOutputStream fos = new FileOutputStream(destFileName);
            //把加密后的字節(jié)寫入到加密文件中
            fos.write(encryptedByte);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        //設(shè)置加密路徑
        ClassLoaderTest classLoaderTest=new ClassLoaderTest("C:\\EncryptedClass\\");
        //將test.class加密后存儲到EncryptedClass目錄下
        classLoaderTest.encryptFile("C:\\Users\\jasonchu.zsy\\IdeaProjects\\BoKeTest\\out\\production\\BoKeTest\\com\\siyu\\test.class"
                ,"C:\\EncryptedClass\\");
        try {
            Class<?> t=classLoaderTest.loadClass("encrytedtest");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}



在main函數(shù)中先將一個編譯好的class文件加密后存儲在非classpath路徑下,然后用自定義classLoader進(jìn)行加載,加密為了簡單起見,使用的是異或加密,利用的原理是二進(jìn)制的數(shù)經(jīng)過兩次異或操作后得到的值是相同的。路徑也使用的絕對路徑,大家可以根據(jù)需要自行進(jìn)行修改,有什么問題可以繼續(xù)交流,謝謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容