日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

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

瀏覽:168日期:2022-08-17 09:53:40

阿里妹導讀: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 來進行加載。

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 對象。

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 = newByteArrayOutputStream()) { 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 方法打印類加載器的信息。

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

執行的結果如下:

TestA: com.java.loader.MyClassLoaderParentFirst@1d44bcfaTestB: 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。

protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader());}2.重寫 loadClass

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

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

測試代碼如下:

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

執行結果如下:

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

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

三 總結

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

以上就是兩種實現Java類隔離加載的方法的詳細內容,更多關于Java類隔離加載的資料請關注好吧啦網其它相關文章!

標簽: Java
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
午夜av一区| 97精品国产| 亚洲国内精品| 欧美日韩视频网站| 国产精品亚洲一区二区三区在线观看| 欧美91在线| 国产精品2023| 精品五月天堂| а√天堂8资源中文在线| 色综合五月天| 色偷偷色偷偷色偷偷在线视频| 美女av在线免费看| 久久久久.com| 92国产精品| 99久久婷婷这里只有精品| 日韩精品首页| 在线日韩av| 亚洲一区激情| 亚洲另类av| 亚洲三级av| 久久精品 人人爱| 麻豆久久久久久久| 老牛影视精品| 亚洲女同一区| 在线看片一区| 国产亚洲精品精品国产亚洲综合| 国产伦精品一区二区三区视频 | 伊人久久婷婷| 亚洲色诱最新| 91精品国产自产在线丝袜啪| 欧美日韩一区二区三区在线电影| 国产激情久久| 特黄特色欧美大片| 亚洲激情国产| 欧美片网站免费| av资源中文在线| 亚洲激情偷拍| 日本不卡视频在线| 精品视频在线观看网站| 香蕉人人精品| 日韩精品国产欧美| 麻豆国产欧美一区二区三区| 久久青草久久| 日韩精品免费观看视频| av资源新版天堂在线| 欧美日韩国产免费观看视频| 视频精品一区| 深夜福利视频一区二区| 免费在线观看日韩欧美| 久久精品资源| 亚洲电影在线一区二区三区| 久久精品99久久久| 久久中文视频| 亚洲精品第一| 福利视频一区| 亚洲精品影视| 日本美女一区| 日韩区欧美区| 黄色在线观看www| 久久最新视频| 精品视频99| 亚洲精品乱码| 日韩久久精品网| 日韩精品免费视频一区二区三区| 日韩欧美一区二区三区免费看| 视频一区视频二区中文| 欧美国产中文高清| 美女网站视频一区| 亚洲1区在线观看| 欧美二三四区| 欧美片第1页综合| 好看的亚洲午夜视频在线| 国产精品2023| 蜜桃免费网站一区二区三区| 亚洲最新无码中文字幕久久| 日韩一区精品| 激情久久久久久| 精品国产中文字幕第一页| 亚洲欧美高清| 久久在线免费| 国产66精品| 国产欧美啪啪| 亚洲一区二区日韩| 亚洲综合电影| 欧美日韩一区二区国产| aⅴ色国产欧美| 日韩久久视频| 欧美激情久久久久久久久久久| 国产亚洲欧洲| 免费在线小视频| 久久91视频| 欧美专区一区| 久久亚洲国产精品一区二区| 日韩精品电影| 国产精品久久| 亚洲资源在线| 尤物在线精品| 亚洲小说欧美另类婷婷| 久久一区视频| 青青国产91久久久久久| 麻豆久久精品| 激情视频一区二区三区| 国产精品久久久久久久免费观看 | 欧美日一区二区在线观看| 伊人成人网在线看| 欧美天堂视频| 国产a亚洲精品| 美女毛片一区二区三区四区最新中文字幕亚洲 | 综合激情婷婷| 国产亚洲精品v| 免费黄色成人| 欧洲精品一区二区三区| 国产精品任我爽爆在线播放| 亚洲日产av中文字幕| 99国产一区| 午夜欧美视频| 欧美/亚洲一区| 伊人久久高清| 日韩一区二区在线免费| 久久97久久97精品免视看秋霞| 欧美精品国产| 久久精品99久久久| 欧美一区二区三区久久精品| 亚州av一区| 日本不卡视频在线观看| 日产欧产美韩系列久久99| 婷婷视频一区二区三区| 中文字幕亚洲影视| 中文不卡在线| 日本aⅴ精品一区二区三区| 婷婷视频一区二区三区| 97久久亚洲| 国产精品色婷婷在线观看| 国产精品v亚洲精品v日韩精品| 国产情侣一区在线| 国产精品久久久久久av公交车| 免费观看亚洲天堂| 精品成av人一区二区三区| 国产中文在线播放| 99久久精品国产亚洲精品| 狠狠爱成人网| 久久最新视频| 日本精品另类| 欧美国产精品| 日韩理论片av| 精品一区毛片| 亚洲一卡久久| 日韩在线黄色| 精品久久久中文字幕| av中文字幕在线观看第一页| 999久久久亚洲| 在线日韩成人| 欧美韩一区二区| 精品日韩视频| 久久国产福利| 国产精品一区二区三区四区在线观看| 美女视频黄久久| 日韩成人三级| 红桃视频国产精品| 欧美一区二区三区久久精品| 久久亚洲精品中文字幕| 久久久久免费av| 三级在线观看一区二区| 国产欧美在线观看免费| 98精品久久久久久久| 91精品啪在线观看国产18 | 激情欧美一区| 亚欧洲精品视频在线观看| 久久成人高清| 亚洲福利国产| 欧美永久精品| 日韩在线综合| 伊人精品久久| 色婷婷综合网| 亚洲网址在线观看| 精品视频在线你懂得| 亚洲特级毛片| 69堂精品视频在线播放| 国产成人精品一区二区三区在线| 欧美成人国产| 久久狠狠久久| 亚洲二区在线| 国产欧美日韩在线一区二区 | 欧美女激情福利| 国产亚洲精品美女久久| 色婷婷狠狠五月综合天色拍| 四虎精品一区二区免费| 四虎国产精品免费观看| www成人在线视频| 青青草国产成人99久久| 久久婷婷久久| 欧美日韩夜夜| 亚洲国产一区二区三区在线播放| 欧美日韩一区二区三区四区在线观看| 日韩欧美在线中字| 日韩高清电影免费| 国产真实久久| 欧美91在线| 一区二区三区四区日韩| 欧美天堂视频|