Java語言十五講(第二講 ClassLoader 2.2)

 

寫在前面:但願你們能夠踊躍發言,你們閱讀後有什麼感覺或者心得體會以及建議均可以在下方留言板小程序內留言的,根據你的留言,郭老師會及時調整講解內容作一些優化和改進,得不到你們的反饋,不知道講解是否有問題,是否符合你們的口味以及可否幫助到你們java

 

首先要明白一個問題:爲何要寫本身的Class Loader?無論那麼多不是同樣好好在用嗎?通常狀況確實是這樣,不過有些時候爲了一些特殊需求,咱們會用到本身定製的Class Loader。好比1998年,Sun內部爲完成JDK1.2忙得熱火朝天,我也在裏面打醬油,咱們有一個小組提供一個工具,給Java生成的字節碼加密,緣由是字節碼太規整,用一些工具很容易反編譯,反編譯以後的結果很容易供人看懂(比許多程序員手工編的程序還容易懂),致使知識產權保護不力。因而就想着把.class文件加密,可是一個加密以後的.class文件確定又不能被正確加載。怎麼辦呢?就要本身作一個Class Loader,拿到.class文件,先進行一步解密,解密以後就成了正常的字節碼,就能夠用普通的application class loader的方式去繼續加載了。程序員

還有一些別的場景,也須要咱們自定義Class Loader,如動態生成的class文件,如多版本class運行。編程

咱們先來看,從哪裏下手作這個工做。小程序

從大的過程,對象的建立分紅兩大步驟,一個是類級別的工做,一個是對象的實例化。顯然,咱們要在類級別工做着一個步驟動腦筋。而在類級別的工做中,分紅加載Loading,連接Linking,初始化Initialization三步。根據上面講解的一些知識,咱們應該在Loading過程當中搞一點名堂。數組

回到ClassLoader的定義,咱們來看提供了哪些方法。網絡

public Class<?> loadClass(String name, boolean resolve) throwsClassNotFoundException

這個loadClass()方法根據類的全名加載類。既然是這樣,那就應該從這裏下手了。咱們看看源碼:app

protectedClass<?> 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 ifclass not found

            // from the non-null parent classloader

        }

        if (c == null) {

            // If still not found, then invokefindClass in order

            // to find the class.

            c = findClass(name);

        }

   }

   if (resolve) {

        resolveClass(c);

   }

   return c;

}

}

咱們能夠看到這個過程,先看這個類是否是已經加載了,若是沒有就讓上層的class loader去加載,最後由本身加載findClass(name)。框架

事情就追到findClass(name)方法了。看它的定義:ide

protected Class<?> findClass(String name) throwsClassNotFoundException

在ClassLoader裏面,它只是一個空的方法,沒有作實現。那麼咱們就能夠實現它來進行咱們本身的工做了。工具

好,咱們在findClass()裏面作什麼呢?天然先要從外部如文件系統或者網絡獲取.class字節流,而後呢?咱們本身把它弄成類模型嗎?理論上是,可是實際上咱們不須要。接着找ClassLoader爲咱們提供了什麼,咱們能夠看到有一個defineClass()方法,定義以下:

protected final Class<?>defineClass(  String name, byte[] b, intoff, int len) throws ClassFormatError

這個方法就是用於將字節流轉成類的。

而且這個方法是final的,咱們用它而且只能用它。

有了這個方法,咱們本身的工做就簡單了,只須要對字節流進行處理後交給defineClass()就行了。

咱們能夠動手編程序了。

先作一個類,MyClass.java,代碼以下:

package com.demo;

public class MyClass {

         public MyClass() {

         }

         public void show() {

       System.out.println("show test!");

    }

}

很簡單,不解釋了。

 

接着咱們寫本身的classloader,爲了簡單起見,咱們的這個class loader不進行任何加密處理,只是簡單地讀取.class文件,生成類,模仿標準的AppClassLoader的行爲。MyClassLoader.java代碼以下:

package com.demo;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class MyClassloader extends ClassLoader {

         @Override

         protectedClass<?> findClass(String name) throws ClassNotFoundException {

                   StringclassFileName = name.replace(".","/") + ".class";

                    byte[] cLassBytes =null;

                    Path path = null;

        try {

                path =Paths.get(getResource(classFileName).toURI());

                cLassBytes =Files.readAllBytes(path);

        } catch (IOException |URISyntaxException e) {

               e.printStackTrace();

        }

        Class<?> clazz = defineClass(name,cLassBytes, 0, cLassBytes.length);

                return clazz;

         }

}

正如上面根據原理分析的,咱們本身寫的class loader override了findClass(),這裏面咱們作了幾步:

一是根據class名字拼出文件名,StringclassFileName = name.replace(".","/") + ".class";;

二是根據名字定位到文件路徑,path = Paths.get(getResource(classFileName).toURI());;

三是把文件讀到字節數組中,cLassBytes= Files.readAllBytes(path);;

四是調用defineClass()生成類,Class<?>clazz = defineClass(name, cLassBytes, 0, cLassBytes.length);。

 

有了這個自定義的classloader以後,咱們寫一個測試類,MyClassTest.java,代碼以下:

package com.demo;

import java.lang.reflect.Method;

public class MyClassTest {

    public static voidmain(String[] args) throws ClassNotFoundException {

        MyClassloader loader =new MyClassloader();

        Class<?> aClass= loader.findClass("com.demo.MyClass");

        try {

            Object obj =aClass.newInstance();

            Method method =aClass.getMethod("show");

           method.invoke(obj);

        } catch (Exception e){

           e.printStackTrace();

        }

    }

}

在程序中,咱們新建一個自定義的類加載器MyClassloader loader = new MyClassloader();

而後調用findClass()去加載類Class<?>aClass = loader.findClass("com.demo.MyClass");

以後根據類定義建立新的對象實例並調用方法:

        Object obj =aClass.newInstance();

        Method method =aClass.getMethod("show");

        method.invoke(obj);

試着運行一下,程序正確出告終果。

到了這一步,恭喜你!你已經知道如何作Class Loader了,We Made It!

 

再回到加密的問題,這一講不講加密自己,可是咱們要根據上面的代碼知道如何處理加密。看咱們本身的findClass()的實現,裏面有兩句話:

cLassBytes = Files.readAllBytes(path);

Class<?> clazz = defineClass(name, cLassBytes, 0, cLassBytes.length);

在defineClass以前,咱們要準備出一個正常的字節數組,所以對於一個加密的.class文件,咱們只須要在以前處理進行解密便可:

cLassBytes = decrypt(Files.readAllBytes(path));

Class<?> clazz = defineClass(name, cLassBytes, 0,cLassBytes.length);

假定有一個decrypt()方法,咱們就能夠了作到了。

 

之前在Sun公司的時候,一個同事曾經說過class loader就像是一個湯匙,用它來抓湯圓吃。這個比喻真有點像。不過這個比喻不夠,由於class loader不光是加載類,還規定了命名空間,不一樣的class loader加載的類是不能互相訪問的(正由於這樣纔會有同一個類的多版本支持的技術)。咱們能夠把class loader圈起來的這片空間理解爲class園地。所以,class loader更加像是一個碗,把湯圓盛到裏頭了。

這個話題超越了進階階段,先按下不表。不過到了這裏,咱們應該能感覺到Class Loader的強大了,從心底裏對Java的發明人一次次投以敬佩的目光,他們二十幾年前居然有如此深入的洞察。對咱們Java程序員來說,James Gosling就是咱們的上帝。在將來的講座裏,我還會跟你們一次次展現出造物者的匠心獨運。

 

學習的過程,是一步步深刻的。不少人工做多年,並無深刻,有些時候也不是由於不努力,而是由於他們在的編程任務老是在無休止地對付客戶需求的變動,沒有坐下來好好整理。而從應用的層面,學習技術就成了追逐新的熱點,不斷地換最新推出的環境框架工具。而寫應用程序,大致上用不到深刻的內容,非專業的人突擊學幾個月以後也能一塊兒作編程了,有的時候比本專業的人作得還要好。那麼學習本專業的價值在哪裏?這是許多年輕人曾經的疑惑。或許與國家的發展階段有關,前些年都是拿別人現成的平臺框架工具,或開源或盜版,本身針對客戶須要快速搭建應用系統,賺快錢,如今慢慢認識到基礎的重要性了,真正願意深刻理解技術的人和公司也多起來了。這對於但願掌握和使用更深技術的程序員是一件好事情。

從事技術工做是一個良心活兒,學習技術則是一個慢工夫,急不來,就得要老老實實一個課題一個課題解決掉。只要方向對,紮紮實實,總能有成。

先賢語錄:不積跬步,無以致千里。

相關文章
相關標籤/搜索