ClassLoader和類加載機(jī)制

1坪哄、背景

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

2、類加載與類加載器

類加載:

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

1、加載:

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

2霎冯、驗(yàn)證

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

3、準(zhǔn)備

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

4、解析

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

5墓卦、初始化

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

該過(guò)程如圖所示:


0_1319366219Na16.gif
0_1319366219Na16.gif

類加載器:

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

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è)計(jì)模式,即傳入一個(gè)類限定名,逐層向上到Bootstrap Class Loader中查找,如果找到即返回,若沒(méi)有找到,則在Extention Class Loader中找,若還沒(méi)有找到則在System Class Loader下找,即classpath中,如果還沒(méi)有找到,則調(diào)用findClass(name)方法,執(zhí)行用戶自己的類加載邏輯(可能在其他的地方)

ClassLoader中的幾個(gè)重要的方法:

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

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

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

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

3损离、廢話少說(shuō)上代碼!

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

package com.siyu;


import java.io.*;

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

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

    /**
     * 重寫(xiě)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實(shí)現(xiàn)了鏈接階段的驗(yà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é)寫(xiě)入到加密文件中
            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加密后存儲(chǔ)到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ù)中先將一個(gè)編譯好的class文件加密后存儲(chǔ)在非classpath路徑下砸捏,然后用自定義classLoader進(jìn)行加載,加密為了簡(jiǎn)單起見(jiàn)隙赁,使用的是異或加密垦藏,利用的原理是二進(jìn)制的數(shù)經(jīng)過(guò)兩次異或操作后得到的值是相同的。路徑也使用的絕對(duì)路徑伞访,大家可以根據(jù)需要自行進(jìn)行修改掂骏,有什么問(wèn)題可以繼續(xù)交流,謝謝厚掷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弟灼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子冒黑,更是在濱河造成了極大的恐慌田绑,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抡爹,死亡現(xiàn)場(chǎng)離奇詭異掩驱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)冬竟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)欧穴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人诱咏,你說(shuō)我怎么就攤上這事苔可。” “怎么了袋狞?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵焚辅,是天一觀的道長(zhǎng)映屋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)同蜻,這世上最難降的妖魔是什么棚点? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮湾蔓,結(jié)果婚禮上瘫析,老公的妹妹穿的比我還像新娘。我一直安慰自己默责,他們只是感情好贬循,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著桃序,像睡著了一般杖虾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上媒熊,一...
    開(kāi)封第一講書(shū)人閱讀 51,610評(píng)論 1 305
  • 那天奇适,我揣著相機(jī)與錄音,去河邊找鬼芦鳍。 笑死嚷往,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的柠衅。 我是一名探鬼主播皮仁,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼茄茁!你這毒婦竟也來(lái)了魂贬?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤裙顽,失蹤者是張志新(化名)和其女友劉穎付燥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體愈犹,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡键科,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漩怎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勋颖。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖勋锤,靈堂內(nèi)的尸體忽然破棺而出饭玲,到底是詐尸還是另有隱情,我是刑警寧澤叁执,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布茄厘,位于F島的核電站矮冬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏次哈。R本人自食惡果不足惜胎署,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窑滞。 院中可真熱鬧琼牧,春花似錦、人聲如沸哀卫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)此改。三九已至抱究,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間带斑,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工勋拟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勋磕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓敢靡,卻偏偏與公主長(zhǎng)得像挂滓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子啸胧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容