最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

兩種實(shí)現(xiàn)Java類隔離加載的方法

 更新時(shí)間:2021年02月06日 09:05:02   作者:肖漢松  
這篇文章主要介紹了兩種實(shí)現(xiàn)Java類隔離加載的方法,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下

阿里妹導(dǎo)讀:Java 開發(fā)中,如果不同的 jar 包依賴了某些通用 jar 包的版本不一樣,運(yùn)行時(shí)就會(huì)因?yàn)榧虞d的類跟預(yù)期不符合導(dǎo)致報(bào)錯(cuò)。如何避免這種情況呢?本文通過分析 jar 包產(chǎn)生沖突的原因及類隔離的實(shí)現(xiàn)原理,分享兩種實(shí)現(xiàn)自定義類加載器的方法。

一  什么是類隔離技術(shù)

只要你 Java 代碼寫的足夠多,就一定會(huì)出現(xiàn)這種情況:系統(tǒng)新引入了一個(gè)中間件的 jar 包,編譯的時(shí)候一切正常,一運(yùn)行就報(bào)錯(cuò):java.lang.NoSuchMethodError,然后就哼哧哼哧的開始找解決方法,最后在幾百個(gè)依賴包里面找的眼睛都快瞎了才找到?jīng)_突的 jar,把問題解決之后就開始吐槽中間件為啥搞那么多不同版本的 jar,寫代碼五分鐘,排包排了一整天。

上面這種情況就是 Java 開發(fā)過程中常見的情況,原因也很簡單,不同 jar 包依賴了某些通用 jar 包(如日志組件)的版本不一樣,編譯的時(shí)候沒問題,到了運(yùn)行時(shí)就會(huì)因?yàn)榧虞d的類跟預(yù)期不符合導(dǎo)致報(bào)錯(cuò)。舉個(gè)例子:A 和 B 分別依賴了 C 的 v1 和 v2 版本,v2 版本的 Log 類比 v1 版本新增了 error 方法,現(xiàn)在工程里面同時(shí)引入了 A、B 兩個(gè) jar 包,以及 C 的 v0.1、v0.2 版本,打包的時(shí)候 maven 只能選擇一個(gè) C 的版本,假設(shè)選擇了 v1 版本。到了運(yùn)行的時(shí)候,默認(rèn)情況下一個(gè)項(xiàng)目的所有類都是用同一個(gè)類加載器加載的,所以不管你依賴了多少個(gè)版本的 C,最終只會(huì)有一個(gè)版本的 C 被加載到 JVM 中。當(dāng) B 要去訪問 Log.error,就會(huì)發(fā)現(xiàn) Log 壓根就沒有 error 方法,然后就拋異常java.lang.NoSuchMethodError。這就是類沖突的一個(gè)典型案例。

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

為了避免兩難選擇,有人就提出了類隔離技術(shù)來解決類沖突的問題。類隔離的原理也很簡單,就是讓每個(gè)模塊使用獨(dú)立的類加載器來加載,這樣不同模塊之間的依賴就不會(huì)互相影響。如下圖所示,不同的模塊用不同的類加載器加載。為什么這樣做就能解決類沖突呢?這里用到了 Java 的一個(gè)機(jī)制:不同類加載器加載的類在 JVM 看來是兩個(gè)不同的類,因?yàn)樵?JVM 中一個(gè)類的唯一標(biāo)識(shí)是 類加載器+類名。通過這種方式我們就能夠同時(shí)加載 C 的兩個(gè)不同版本的類,即使它類名是一樣的。注意,這里類加載器指的是類加載器的實(shí)例,并不是一定要定義兩個(gè)不同類加載器,例如圖中的 PluginClassLoaderA 和 PluginClassLoaderB 可以是同一個(gè)類加載器的不同實(shí)例。

二  如何實(shí)現(xiàn)類隔離

前面我們提到類隔離就是讓不同模塊的 jar 包用不同的類加載器加載,要做到這一點(diǎn),就需要讓 JVM 能夠使用自定義的類加載器加載我們寫的類以及其關(guān)聯(lián)的類。

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

實(shí)際上 JVM 提供了一種非常簡單有效的方式,我把它稱為類加載傳導(dǎo)規(guī)則:JVM 會(huì)選擇當(dāng)前類的類加載器來加載所有該類的引用的類。例如我們定義了 TestA 和 TestB 兩個(gè)類,TestA 會(huì)引用 TestB,只要我們使用自定義的類加載器加載 TestA,那么在運(yùn)行時(shí),當(dāng) TestA 調(diào)用到 TestB 的時(shí)候,TestB 也會(huì)被 JVM 使用 TestA 的類加載器加載。依此類推,只要是 TestA 及其引用類關(guān)聯(lián)的所有 jar 包的類都會(huì)被自定義類加載器加載。通過這種方式,我們只要讓模塊的 main 方法類使用不同的類加載器加載,那么每個(gè)模塊的都會(huì)使用 main 方法類的類加載器加載的,這樣就能讓多個(gè)模塊分別使用不同類加載器。這也是 OSGi 和 SofaArk 能夠?qū)崿F(xiàn)類隔離的核心原理。

了解了類隔離的實(shí)現(xiàn)原理之后,我們從重寫類加載器開始進(jìn)行實(shí)操。要實(shí)現(xiàn)自己的類加載器,首先讓自定義的類加載器繼承 java.lang.ClassLoader,然后重寫類加載的方法,這里我們有兩個(gè)選擇,一個(gè)是重寫 findClass(String name),一個(gè)是重寫 loadClass(String name)。那么到底應(yīng)該選擇哪個(gè)?這兩者有什么區(qū)別?
下面我們分別嘗試重寫這兩個(gè)方法來實(shí)現(xiàn)自定義類加載器。

1.重寫 findClass

首先我們定義兩個(gè)類,TestA 會(huì)打印自己的類加載器,然后調(diào)用 TestB 打印它的類加載器,我們預(yù)期是實(shí)現(xiàn)重寫了 findClass 方法的類加載器 MyClassLoaderParentFirst 能夠在加載了 TestA 之后,讓 TestB 也自動(dòng)由 MyClassLoaderParentFirst 來進(jìn)行加載。

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 方法,這個(gè)方法先根據(jù)文件路徑加載 class 文件,然后調(diào)用 defineClass 獲取 Class 對(duì)象。

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[] {};
  }
}

最后寫一個(gè) main 方法調(diào)用自定義的類加載器加載 TestA,然后通過反射調(diào)用 TestA 的 main 方法打印類加載器的信息。

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});
  }

執(zhí)行的結(jié)果如下:

TestA: com.java.loader.MyClassLoaderParentFirst@1d44bcfa
TestB: sun.misc.Launcher$AppClassLoader@18b4aac2

執(zhí)行的結(jié)果并沒有如我們期待,TestA 確實(shí)是 MyClassLoaderParentFirst 加載的,但是 TestB 還是 AppClassLoader 加載的。這是為什么呢?

要回答這個(gè)問題,首先是要了解一個(gè)類加載的規(guī)則:JVM 在觸發(fā)類加載時(shí)調(diào)用的是 ClassLoader.loadClass 方法。這個(gè)方法的實(shí)現(xiàn)了雙親委派:

  • 委托給父加載器查詢
  • 如果父加載器查詢不到,就調(diào)用 findClass 方法進(jìn)行加載

明白了這個(gè)規(guī)則之后,執(zhí)行的結(jié)果的原因就找到了:JVM 確實(shí)使用了MyClassLoaderParentFirst 來加載 TestB,但是因?yàn)殡p親委派的機(jī)制,TestB 被委托給了 MyClassLoaderParentFirst 的父加載器 AppClassLoader 進(jìn)行加載。

你可能還好奇,為什么 MyClassLoaderParentFirst 的父加載器是 AppClassLoader?因?yàn)槲覀兌x的 main 方法類默認(rèn)情況下都是由 JDK 自帶的 AppClassLoader 加載的,根據(jù)類加載傳導(dǎo)規(guī)則,main 類引用的 MyClassLoaderParentFirst 也是由加載了 main 類的AppClassLoader 來加載。由于 MyClassLoaderParentFirst 的父類是 ClassLoader,ClassLoader 的默認(rèn)構(gòu)造方法會(huì)自動(dòng)設(shè)置父加載器的值為 AppClassLoader。

protected ClassLoader() {
  this(checkCreateClassLoader(), getSystemClassLoader());
}

2.重寫 loadClass

由于重寫 findClass 方法會(huì)受到雙親委派機(jī)制的影響導(dǎo)致 TestB 被 AppClassLoader 加載,不符合類隔離的目標(biāo),所以我們只能重寫 loadClass 方法來破壞雙親委派機(jī)制。代碼如下所示:

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) { //省略 }

}

這里注意一點(diǎn),我們重寫了 loadClass 方法也就是意味著所有類包括 java.lang 包里面的類都會(huì)通過 MyClassLoaderCustom 進(jìn)行加載,但類隔離的目標(biāo)不包括這部分 JDK 自帶的類,所以我們用 ExtClassLoader 來加載 JDK 的類,相關(guān)的代碼就是:result = jdkClassLoader.loadClass(name);

測試代碼如下:

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});
  }
}

執(zhí)行結(jié)果如下:

TestA: com.java.loader.MyClassLoaderCustom@1d44bcfa
TestB: com.java.loader.MyClassLoaderCustom@1d44bcfa

可以看到,通過重寫了 loadClass 方法,我們成功的讓 TestB 也使用MyClassLoaderCustom 加載到了 JVM 中。

三  總結(jié)

類隔離技術(shù)是為了解決依賴沖突而誕生的,它通過自定義類加載器破壞雙親委派機(jī)制,然后利用類加載傳導(dǎo)規(guī)則實(shí)現(xiàn)了不同模塊的類隔離。

以上就是兩種實(shí)現(xiàn)Java類隔離加載的方法的詳細(xì)內(nèi)容,更多關(guān)于Java類隔離加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 簡單了解Java方法的定義和使用實(shí)現(xiàn)詳解

    簡單了解Java方法的定義和使用實(shí)現(xiàn)詳解

    這篇文章主要介紹了簡單了解Java方法的定義和使用實(shí)現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • 優(yōu)化SpringBoot程序啟動(dòng)速度的實(shí)現(xiàn)

    優(yōu)化SpringBoot程序啟動(dòng)速度的實(shí)現(xiàn)

    本文主要介紹了優(yōu)化SpringBoot程序啟動(dòng)速度的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • 拳皇(Java簡單的小程序)代碼實(shí)例

    拳皇(Java簡單的小程序)代碼實(shí)例

    這篇文章主要介紹了拳皇Java簡單小程序,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • 多模塊maven的deploy集成gitlab?ci自動(dòng)發(fā)版配置

    多模塊maven的deploy集成gitlab?ci自動(dòng)發(fā)版配置

    這篇文章主要為大家介紹了多模塊maven項(xiàng)目deploy集成gitlab?ci自動(dòng)發(fā)版的配置流程步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-02-02
  • SpringBoot配置使用H2數(shù)據(jù)庫的簡單教程

    SpringBoot配置使用H2數(shù)據(jù)庫的簡單教程

    H2是一個(gè)Java編寫的關(guān)系型數(shù)據(jù)庫,它可以被嵌入Java應(yīng)用程序中使用,或者作為一個(gè)單獨(dú)的數(shù)據(jù)庫服務(wù)器運(yùn)行。本文將介紹SpringBoot如何配置使用H2數(shù)據(jù)庫
    2021-05-05
  • MybatisPlus處理大表查詢的實(shí)現(xiàn)步驟

    MybatisPlus處理大表查詢的實(shí)現(xiàn)步驟

    在實(shí)際工作中當(dāng)指定查詢數(shù)據(jù)過大時(shí),我們一般使用分頁查詢的方式一頁一頁的將數(shù)據(jù)放到內(nèi)存處理,本文主要介紹了MybatisPlus處理大表查詢的實(shí)現(xiàn)步驟,感興趣的可以了解一下
    2024-08-08
  • springboot自動(dòng)重啟的簡單方法

    springboot自動(dòng)重啟的簡單方法

    Springboot提供了熱部署的方式,當(dāng)發(fā)現(xiàn)任何類發(fā)生了改變,馬上通過JVM類加載的方式,加載最新的類到虛擬機(jī)中。這篇文章主要介紹了springboot自動(dòng)重啟的實(shí)現(xiàn)方法,需要的朋友可以參考下
    2018-04-04
  • idea安裝與配置及基本用法教程詳解

    idea安裝與配置及基本用法教程詳解

    這篇文章主要介紹了idea安裝與配置及基本用法教程詳解,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • UrlDecoder和UrlEncoder使用詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    UrlDecoder和UrlEncoder使用詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要為大家詳細(xì)介紹了UrlDecoder和UrlEncoder使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Java中處理郵件附件的方法(本地文件和在線URL)

    Java中處理郵件附件的方法(本地文件和在線URL)

    這篇文章主要介紹了在Java中處理郵件附件的方法,針對(duì)本地文件和在線URL兩種附件來源,提供了兩種解決方案,并提供了下載文件和發(fā)送郵件的代碼示例,需要的朋友可以參考下
    2025-03-03

最新評(píng)論

林甸县| 侯马市| 观塘区| 镇安县| 龙川县| 咸宁市| 桐梓县| 岱山县| 星座| 西藏| 长子县| 新兴县| 大庆市| 河曲县| 沅陵县| 宁陵县| 庆云县| 措勤县| 榕江县| 河东区| 精河县| 江都市| 大洼县| 肇源县| 中超| 汶川县| 三明市| 德兴市| 漳浦县| 木里| 阜平县| 维西| 元阳县| 磐安县| 峨眉山市| 绿春县| 山阴县| 南江县| 巢湖市| 灯塔市| 琼海市|