Java中ClassLoader類加載的示例分析

這篇文章主要為大家展示了“Java中ClassLoader類加載的示例分析”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Java中ClassLoader類加載的示例分析”這篇文章吧。

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、小程序開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了上杭免費(fèi)建站歡迎大家使用!

雙親委派模型

類加載這個(gè)概念應(yīng)該算是Java語言的一種創(chuàng)新,目的是為了將類的加載過程與虛擬機(jī)解耦,達(dá)到”通過類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流“的目的。實(shí)現(xiàn)這個(gè)功能的代碼模塊就是類加載器。類加載器的基本模型就是大名鼎鼎的雙親委派模型(Parents Delegation Model)。聽上去很牛掰,其實(shí)邏輯很簡(jiǎn)單,在需要加載一個(gè)類的時(shí)候,我們首先判斷該類是否已被加載,如果沒有就判斷是否已被父加載器加載,如果還沒有再調(diào)用自己的findClass方法嘗試加載?;镜哪P途褪沁@樣(盜圖侵刪):

Java中ClassLoader類加載的示例分析

實(shí)現(xiàn)起來也很簡(jiǎn)單,重點(diǎn)就是ClassLoader類的loadClass方法,源碼如下:

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) {
      
      Class(c);
    }
    return c;
  }
}

突然感覺被逗了,怎么默認(rèn)直接就拋了異常呢?其實(shí)是因?yàn)镃lassLoader這個(gè)類是一個(gè)抽象類,實(shí)際在使用時(shí)候會(huì)寫個(gè)子類,這個(gè)方法會(huì)按照需要被重寫,來完成業(yè)務(wù)需要的加載過程。

自定義ClassLoader

在自定義ClassLoader的子類時(shí)候,我們常見的會(huì)有兩種做法,一種是重寫loadClass方法,另一種是重寫findClass方法。其實(shí)這兩種方法本質(zhì)上差不多,畢竟loadClass也會(huì)調(diào)用findClass,但是從邏輯上講我們最好不要直接修改loadClass的內(nèi)部邏輯。

個(gè)人認(rèn)為比較好的做法其實(shí)是只在findClass里重寫自定義類的加載方法。

為啥說這種比較好呢,因?yàn)榍懊嫖乙舱f道,loadClass這個(gè)方法是實(shí)現(xiàn)雙親委托模型邏輯的地方,擅自修改這個(gè)方法會(huì)導(dǎo)致模型被破壞,容易造成問題。因此我們最好是在雙親委托模型框架內(nèi)進(jìn)行小范圍的改動(dòng),不破壞原有的穩(wěn)定結(jié)構(gòu)。同時(shí),也避免了自己重寫loadClass方法的過程中必須寫雙親委托的重復(fù)代碼,從代碼的復(fù)用性來看,不直接修改這個(gè)方法始終是比較好的選擇。

當(dāng)然,如果是刻意要破壞雙親委托模型就另說。

破壞雙親委托模型

為什么要破壞雙親委托模型呢?

其實(shí)在某些情況下,我們可能需要加載兩個(gè)不同的類,但是不巧的是這兩個(gè)類的名字完全一樣,這時(shí)候雙親委托模型就無法滿足我們的要求了,我們就要重寫loadClass方法破壞雙親委托模型,讓同一個(gè)類名加載多次。當(dāng)然,這里說的破壞只是局部意義上的破壞。

但是類名相同了,jvm怎么區(qū)別這兩個(gè)類呢?顯然,這并不會(huì)造成什么世界觀的崩塌,其實(shí)類在jvm里并不僅是通過類名來限定的,他還屬于加載他的ClassLoader。由不同ClassLoader加載的類其實(shí)是互不影響的。

做一個(gè)實(shí)驗(yàn)。

我們先寫兩個(gè)類:

package com.mythsman.test;
public class Hello {
  public void say() {
    System.out.println("This is from Hello v1");
  }
}
package com.mythsman.test;
public class Hello {
  public void say() {
    System.out.println("This is from Hello v2");
  }
}

兩個(gè)類名字一樣,唯一的區(qū)別是方法的實(shí)現(xiàn)不一樣。我們先分別編譯,然后把生成的class文件重命名為Hello.class.1和Hello.class.2。

我們的目的是希望能在測(cè)試類里分別創(chuàng)建這兩個(gè)類的實(shí)例。

接著我們新建一個(gè)測(cè)試類com.mythsman.test.Main,在主函數(shù)里創(chuàng)建兩個(gè)自定義的ClassLoader:

ClassLoader classLoader1=new ClassLoader() {
  @Override
  public Class<?> loadClass(String s) throws ClassNotFoundException {
    try {
      if (s.equals("com.mythsman.test.Hello")) {
        byte[] classBytes = Files.readAllBytes(Paths.get("/home/myths/Desktop/test/Hello.class.1"));
        return defineClass(s, classBytes, 0, classBytes.length);
      }else{
        return super.loadClass(s);
      }
    }catch (IOException e) {
      throw new ClassNotFoundException(s);
    }
  }
};
ClassLoader classLoader2=new ClassLoader() {
  @Override
  public Class<?> loadClass(String s) throws ClassNotFoundException {
    try {
      if (s.equals("com.mythsman.test.Hello")) {
        byte[] classBytes = Files.readAllBytes(Paths.get("/home/myths/Desktop/test/Hello.class.2"));
        return defineClass(s, classBytes, 0, classBytes.length);
      }else{
        return super.loadClass(s);
      }
    }catch (IOException e) {
      throw new ClassNotFoundException(s);
    }
  }
};

這兩個(gè)ClassLoader的用途就是分別關(guān)聯(lián)Hello類的兩種不同字節(jié)碼,我們需要讀取字節(jié)碼文件并通過defineClass方法加載成class。注意我們重載的是loadClass方法,如果是重載findClass方法那么由于loadClass方法的雙親委托處理機(jī)制,第二個(gè)ClassLoader的findClass方法其實(shí)并不會(huì)被調(diào)用。

那我們?cè)趺瓷蓪?shí)例呢?顯然我們不能直接用類名來引用(名稱沖突),那就只能用反射了:

Object helloV1=classLoader1.loadClass("com.mythsman.test.Hello").newInstance();
Object helloV2=classLoader2.loadClass("com.mythsman.test.Hello").newInstance();
helloV1.getClass().getMethod("say").invoke(helloV1);
helloV2.getClass().getMethod("say").invoke(helloV2);

輸出:

This is from Hello v1
This is from Hello v2

OK,這樣就算是完成了兩次加載,但是還有幾個(gè)注意點(diǎn)需要關(guān)注下。

兩個(gè)類的關(guān)系是什么

顯然這兩個(gè)類并不是同一個(gè)類,但是他們的名字一樣,那么類似isinstance of之類的操作符結(jié)果是什么樣的呢:

System.out.println("class:"+helloV1.getClass());
System.out.println("class:"+helloV2.getClass());
System.out.println("hashCode:"+helloV1.getClass().hashCode());
System.out.println("hashCode:"+helloV2.getClass().hashCode());
System.out.println("classLoader:"+helloV1.getClass().getClassLoader());
System.out.println("classLoader:"+helloV2.getClass().getClassLoader());

輸出:

class:class com.mythsman.test.Hello
class:class com.mythsman.test.Hello
hashCode:1581781576
hashCode:1725154839
classLoader:com.mythsman.test.Main$1@5e2de80c
classLoader:com.mythsman.test.Main$2@266474c2

他們的類名的確是一樣的,但是類的hashcode不一樣,也就意味著這兩個(gè)本質(zhì)不是一個(gè)類,而且他們的類加載器也不同(其實(shí)就是Main的兩個(gè)內(nèi)部類)。

這兩個(gè)類加載器跟系統(tǒng)的三層類加載器是什么關(guān)系

以第一個(gè)自定義的類加載器為例:

System.out.println(classLoader1.getParent().getParent().getParent());
System.out.println(classLoader1.getParent().getParent());
System.out.println(classLoader1.getParent());
System.out.println(classLoader1 );
System.out.println(ClassLoader.getSystemClassLoader());

輸出:

null
sun.misc.Launcher$ExtClassLoader@60e53b93
sun.misc.Launcher$AppClassLoader@18b4aac2
com.mythsman.test.Main$1@5e2de80c
sun.misc.Launcher$AppClassLoader@18b4aac2

我們可以看到,第四行就是這個(gè)自定義的ClassLoader,他的父親是AppClassLoader,爺爺是ExtClassLoader,太爺爺是null,其實(shí)就是用C寫的BootStrapClassLoader。而當(dāng)前系統(tǒng)的ClassLoader就是這個(gè)AppClassLoader。

當(dāng)然,這里說的父子關(guān)系并不是繼承關(guān)系,而是組合關(guān)系,子ClassLoader保存了父ClassLoader的一個(gè)引用(parent)。

以上是“Java中ClassLoader類加載的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

本文題目:Java中ClassLoader類加載的示例分析
分享路徑:http://muchs.cn/article10/jpgego.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供虛擬主機(jī)、網(wǎng)站策劃、網(wǎng)站導(dǎo)航網(wǎng)站維護(hù)、品牌網(wǎng)站建設(shè)、定制開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作