激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 兩種實現Java類隔離加載的方法

兩種實現Java類隔離加載的方法

2021-08-04 11:06肖漢松 Java教程

這篇文章主要介紹了兩種實現Java類隔離加載的方法,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下

阿里妹導讀:java 開發中,如果不同的 jar 包依賴了某些通用 jar 包的版本不一樣,運行時就會因為加載的類跟預期不符合導致報錯。如何避免這種情況呢?本文通過分析 jar 包產生沖突的原因及類隔離的實現原理,分享兩種實現自定義類加載器的方法。

一  什么是類隔離技術

 

只要你 java 代碼寫的足夠多,就一定會出現這種情況:系統新引入了一個中間件的 jar 包,編譯的時候一切正常,一運行就報錯:java.lang.nosuchmethoderror,然后就哼哧哼哧的開始找解決方法,最后在幾百個依賴包里面找的眼睛都快瞎了才找到沖突的 jar,把問題解決之后就開始吐槽中間件為啥搞那么多不同版本的 jar,寫代碼五分鐘,排包排了一整天。

上面這種情況就是 java 開發過程中常見的情況,原因也很簡單,不同 jar 包依賴了某些通用 jar 包(如日志組件)的版本不一樣,編譯的時候沒問題,到了運行時就會因為加載的類跟預期不符合導致報錯。舉個例子:a 和 b 分別依賴了 c 的 v1 和 v2 版本,v2 版本的 log 類比 v1 版本新增了 error 方法,現在工程里面同時引入了 a、b 兩個 jar 包,以及 c 的 v0.1、v0.2 版本,打包的時候 maven 只能選擇一個 c 的版本,假設選擇了 v1 版本。到了運行的時候,默認情況下一個項目的所有類都是用同一個類加載器加載的,所以不管你依賴了多少個版本的 c,最終只會有一個版本的 c 被加載到 jvm 中。當 b 要去訪問 log.error,就會發現 log 壓根就沒有 error 方法,然后就拋異常java.lang.nosuchmethoderror。這就是類沖突的一個典型案例。

兩種實現Java類隔離加載的方法

類沖突的問題如果版本是向下兼容的其實很好解決,把低版本的排除掉就完事了。但要是遇到版本不向下兼容的那就陷入了“救媽媽還是救女朋友”的兩難處境了。

為了避免兩難選擇,有人就提出了類隔離技術來解決類沖突的問題。類隔離的原理也很簡單,就是讓每個模塊使用獨立的類加載器來加載,這樣不同模塊之間的依賴就不會互相影響。如下圖所示,不同的模塊用不同的類加載器加載。為什么這樣做就能解決類沖突呢?這里用到了 java 的一個機制:不同類加載器加載的類在 jvm 看來是兩個不同的類,因為在 jvm 中一個類的唯一標識是 類加載器+類名。通過這種方式我們就能夠同時加載 c 的兩個不同版本的類,即使它類名是一樣的。注意,這里類加載器指的是類加載器的實例,并不是一定要定義兩個不同類加載器,例如圖中的 pluginclassloadera 和 pluginclassloaderb 可以是同一個類加載器的不同實例。

兩種實現Java類隔離加載的方法

二  如何實現類隔離

 

前面我們提到類隔離就是讓不同模塊的 jar 包用不同的類加載器加載,要做到這一點,就需要讓 jvm 能夠使用自定義的類加載器加載我們寫的類以及其關聯的類。

那么如何實現呢?一個很簡單的做法就是 jvm 提供一個全局類加載器的設置接口,這樣我們直接替換全局類加載器就行了,但是這樣無法解決多個自定義類加載器同時存在的問題。

實際上 jvm 提供了一種非常簡單有效的方式,我把它稱為類加載傳導規則:jvm 會選擇當前類的類加載器來加載所有該類的引用的類。例如我們定義了 testa 和 testb 兩個類,testa 會引用 testb,只要我們使用自定義的類加載器加載 testa,那么在運行時,當 testa 調用到 testb 的時候,testb 也會被 jvm 使用 testa 的類加載器加載。依此類推,只要是 testa 及其引用類關聯的所有 jar 包的類都會被自定義類加載器加載。通過這種方式,我們只要讓模塊的 main 方法類使用不同的類加載器加載,那么每個模塊的都會使用 main 方法類的類加載器加載的,這樣就能讓多個模塊分別使用不同類加載器。這也是 osgi 和 sofaark 能夠實現類隔離的核心原理。

了解了類隔離的實現原理之后,我們從重寫類加載器開始進行實操。要實現自己的類加載器,首先讓自定義的類加載器繼承 java.lang.classloader,然后重寫類加載的方法,這里我們有兩個選擇,一個是重寫 findclass(string name),一個是重寫 loadclass(string name)。那么到底應該選擇哪個?這兩者有什么區別?
下面我們分別嘗試重寫這兩個方法來實現自定義類加載器。

1.重寫 findclass

首先我們定義兩個類,testa 會打印自己的類加載器,然后調用 testb 打印它的類加載器,我們預期是實現重寫了 findclass 方法的類加載器 myclassloaderparentfirst 能夠在加載了 testa 之后,讓 testb 也自動由 myclassloaderparentfirst 來進行加載。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class testa {
 
  public static void main(string[] args) {
    testa testa = new testa();
    testa.hello();
  }
 
  public void hello() {
    // https://jinglingwang.cn/archives/class-isolation-loading
    system.out.println("testa: " + this.getclass().getclassloader());
    testb testb = new testb();
    testb.hello();
  }
}
 
public class testb {
 
  public void hello() {
    system.out.println("testb: " + this.getclass().getclassloader());
  }
}

然后重寫一下 findclass 方法,這個方法先根據文件路徑加載 class 文件,然后調用 defineclass 獲取 class 對象。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class myclassloaderparentfirst extends classloader{
 
  private map<string, string> classpathmap = new hashmap<>();
 
  public myclassloaderparentfirst() {
    classpathmap.put("com.java.loader.testa", "/users/hansong/ideaprojects/ohmyjava/coderepository/target/classes/com/java/loader/testa.class");
    classpathmap.put("com.java.loader.testb", "/users/hansong/ideaprojects/ohmyjava/coderepository/target/classes/com/java/loader/testb.class");
  }
 
  // 重寫了 findclass 方法  by:jinglingwang.cn
  @override
  public class<?> findclass(string name) throws classnotfoundexception {
    string classpath = classpathmap.get(name);
    file file = new file(classpath);
    if (!file.exists()) {
      throw new classnotfoundexception();
    }
    byte[] classbytes = getclassdata(file);
    if (classbytes == null || classbytes.length == 0) {
      throw new classnotfoundexception();
    }
    return defineclass(classbytes, 0, classbytes.length);
  }
 
  private byte[] getclassdata(file file) {
    try (inputstream ins = new fileinputstream(file); bytearrayoutputstream baos = new
        bytearrayoutputstream()) {
      byte[] buffer = new byte[4096];
      int bytesnumread = 0;
      while ((bytesnumread = ins.read(buffer)) != -1) {
        baos.write(buffer, 0, bytesnumread);
      }
      return baos.tobytearray();
    } catch (filenotfoundexception e) {
      e.printstacktrace();
    } catch (ioexception e) {
      e.printstacktrace();
    }
    return new byte[] {};
  }
}

最后寫一個 main 方法調用自定義的類加載器加載 testa,然后通過反射調用 testa 的 main 方法打印類加載器的信息。

?
1
2
3
4
5
6
7
8
public class mytest {
 
  public static void main(string[] args) throws exception {
    myclassloaderparentfirst myclassloaderparentfirst = new myclassloaderparentfirst();
    class testaclass = myclassloaderparentfirst.findclass("com.java.loader.testa");
    method mainmethod = testaclass.getdeclaredmethod("main", string[].class);
    mainmethod.invoke(null, new object[]{args});
  }

執行的結果如下:

?
1
2
testa: com.java.loader.myclassloaderparentfirst@1d44bcfa
testb: sun.misc.launcher$appclassloader@18b4aac2

執行的結果并沒有如我們期待,testa 確實是 myclassloaderparentfirst 加載的,但是 testb 還是 appclassloader 加載的。這是為什么呢?

要回答這個問題,首先是要了解一個類加載的規則:jvm 在觸發類加載時調用的是 classloader.loadclass 方法。這個方法的實現了雙親委派:

  • 委托給父加載器查詢
  • 如果父加載器查詢不到,就調用 findclass 方法進行加載

明白了這個規則之后,執行的結果的原因就找到了:jvm 確實使用了myclassloaderparentfirst 來加載 testb,但是因為雙親委派的機制,testb 被委托給了 myclassloaderparentfirst 的父加載器 appclassloader 進行加載。

你可能還好奇,為什么 myclassloaderparentfirst 的父加載器是 appclassloader?因為我們定義的 main 方法類默認情況下都是由 jdk 自帶的 appclassloader 加載的,根據類加載傳導規則,main 類引用的 myclassloaderparentfirst 也是由加載了 main 類的appclassloader 來加載。由于 myclassloaderparentfirst 的父類是 classloader,classloader 的默認構造方法會自動設置父加載器的值為 appclassloader。

?
1
2
3
protected classloader() {
  this(checkcreateclassloader(), getsystemclassloader());
}

2.重寫 loadclass

由于重寫 findclass 方法會受到雙親委派機制的影響導致 testb 被 appclassloader 加載,不符合類隔離的目標,所以我們只能重寫 loadclass 方法來破壞雙親委派機制。代碼如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class myclassloadercustom extends classloader {
 
  private classloader jdkclassloader;
 
  private map<string, string> classpathmap = new hashmap<>();
 
  public myclassloadercustom(classloader jdkclassloader) {
    this.jdkclassloader = jdkclassloader;
    classpathmap.put("com.java.loader.testa", "/users/hansong/ideaprojects/ohmyjava/coderepository/target/classes/com/java/loader/testa.class");
    classpathmap.put("com.java.loader.testb", "/users/hansong/ideaprojects/ohmyjava/coderepository/target/classes/com/java/loader/testb.class");
  }
 
  @override
  protected class<?> loadclass(string name, boolean resolve) throws classnotfoundexception {
    class result = null;
    try {
      //by:jinglingwang.cn 這里要使用 jdk 的類加載器加載 java.lang 包里面的類
      result = jdkclassloader.loadclass(name);
    } catch (exception e) {
      //忽略 by:jinglingwang.cn
    }
    if (result != null) {
      return result;
    }
    string classpath = classpathmap.get(name);
    file file = new file(classpath);
    if (!file.exists()) {
      throw new classnotfoundexception();
    }
 
    byte[] classbytes = getclassdata(file);
    if (classbytes == null || classbytes.length == 0) {
      throw new classnotfoundexception();
    }
    return defineclass(classbytes, 0, classbytes.length);
  }
 
  private byte[] getclassdata(file file) { //省略 }
 
}

這里注意一點,我們重寫了 loadclass 方法也就是意味著所有類包括 java.lang 包里面的類都會通過 myclassloadercustom 進行加載,但類隔離的目標不包括這部分 jdk 自帶的類,所以我們用 extclassloader 來加載 jdk 的類,相關的代碼就是:result = jdkclassloader.loadclass(name);

測試代碼如下:

?
1
2
3
4
5
6
7
8
9
10
public class mytest {
 
  public static void main(string[] args) throws exception {
    //這里取appclassloader的父加載器也就是extclassloader作為myclassloadercustom的jdkclassloader
    myclassloadercustom myclassloadercustom = new myclassloadercustom(thread.currentthread().getcontextclassloader().getparent());
    class testaclass = myclassloadercustom.loadclass("com.java.loader.testa");
    method mainmethod = testaclass.getdeclaredmethod("main", string[].class);
    mainmethod.invoke(null, new object[]{args});
  }
}

執行結果如下:

?
1
2
testa: com.java.loader.myclassloadercustom@1d44bcfa
testb: com.java.loader.myclassloadercustom@1d44bcfa

可以看到,通過重寫了 loadclass 方法,我們成功的讓 testb 也使用myclassloadercustom 加載到了 jvm 中。

三  總結

 

類隔離技術是為了解決依賴沖突而誕生的,它通過自定義類加載器破壞雙親委派機制,然后利用類加載傳導規則實現了不同模塊的類隔離。

以上就是兩種實現java類隔離加載的方法的詳細內容,更多關于java類隔離加載的資料請關注服務器之家其它相關文章!

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚洲成人第一页 | 亚洲国产精品99 | 亚洲一区二区三区四区精品 | 欧美毛片 | 999久久久精品 | 精品国产一区二区三区四区阿崩 | 欧美一区二区片 | 久久精品视频5 | 成品片a免费直接观看 | 日韩av有码在线 | 成人资源在线 | 视频一区二区不卡 | 一级大黄毛片 | 久久国产综合精品 | 欧美成人精品一区二区三区 | 久久成人国产精品入口 | 激情亚洲一区二区 | 成人aaaa免费全部观看 | av在线免费观看网站 | 99久久久国产精品免费观看 | 特级a欧美做爰片毛片 | 又黄又爽免费无遮挡在线观看 | 成人午夜高清 | 欧美精品国产综合久久 | 免费国产视频在线观看 | 狠狠婷婷综合久久久久久妖精 | 免费一级a毛片免费观看 | 国产精品成人一区二区三区电影毛片 | 97人操| 久久国产精品99久久人人澡 | 国产中文99视频在线观看 | 成人免费在线视频播放 | 亚洲三区精品 | 九九视频精品在线 | 国产三级在线观看a | 亚洲国产精品久久久久婷婷老年 | 成人毛片视频免费 | 欧美成人免费在线视频 | 黄色二区三区 | 国产精品久久久久久久久久久久久久久久 | 成片免费观看大全 |