Java通過自定義類加載器實現(xiàn)類隔離
前言
由于微服務(wù)的快速迭代、持續(xù)集成等特性,越來越多的團隊更傾向于它。但是也體現(xiàn)出了一些問題,比如在基礎(chǔ)設(shè)施建設(shè)過程中,需要把通用功能下沉,把現(xiàn)有大而全的基礎(chǔ)設(shè)施按領(lǐng)域拆分,考慮需要兼容現(xiàn)有生產(chǎn)服務(wù),會產(chǎn)生不同的依賴版本,有時不注意就可以引發(fā)問題。比如本文遇到的依賴包版本沖突問題,以及如何利用類隔離技術(shù)解決的分析。
類隔離是什么
類隔離是一種通過類加載器實現(xiàn)加載所需類的實現(xiàn)方式,使得不同版本類間隔離,避免了使用沖突問題,最終的效果就是不同模塊的內(nèi)容被不同的類加載器加載,滿足同一環(huán)境下同時兼容不同接口實現(xiàn)類。
使用場景
比如業(yè)務(wù)服務(wù)A和業(yè)務(wù)服務(wù)B均需要消息通知等,均依賴消息中間件,但所引用版本不一致,導(dǎo)致最終只有一個版本加載到JVM,在某一個服務(wù)調(diào)用時會出現(xiàn) NoSuchMethodError或NoSuchClassError問題,這就很難排查出來,沒準(zhǔn)會影響項目進度,最終月度的績效(“雞腿”)不保。
服務(wù)A pom.xml:
??<!--?common-message--> ????????<dependency> ????????????<groupId>com.lgy</groupId> ????????????<artifactId>spring-common-message</artifactId> ????????????<version>1.0.0<version> ????????</dependency>
服務(wù)B pom.xml:
??<!--?common-message--> ????????<dependency> ????????????<groupId>com.lgy</groupId> ????????????<artifactId>spring-common-message</artifactId> ????????????<version>2.0.0<version> ????????</dependency>
業(yè)務(wù)調(diào)用流程:
?//?業(yè)務(wù)A調(diào)用微信服務(wù)通知 ?MessageUtil.sendMessage(content,peopleId,templateId,"wechat"); ?//?業(yè)務(wù)B調(diào)用微信服務(wù)通知 ?MessageUtil.sendToWechat(content,peopleId,templateId);
JVM最終加載的為 2.0.0 版本的依賴,導(dǎo)致業(yè)務(wù)A在調(diào)用時拋異常java.lang.NoSuchMethodError。
解決方案
大體的解決思路就是,在不改變業(yè)務(wù)代碼的前提下, 業(yè)務(wù)A調(diào)用 1.0.0 版本的消息工具類, 業(yè)務(wù)B調(diào)用2.0.0版本的消息工具類,因此需要JVM能夠利用自定義類加載器加載所需的類或關(guān)聯(lián)的類。
實現(xiàn)思路
重寫類加載器,實現(xiàn)自定義類加載(java.lang.ClassLoader)
重寫類加載函數(shù)
- 重寫 findClass(String name)
- 重寫 loadClass(String name)
涉及的知識點
- JVM加載過程:加載-》鏈接-》初始化(具體后續(xù)介紹)
- 雙親委派機制:委托父加載器查詢;如果父加載器查詢不到,則調(diào)用自身的findClass加載
重寫findClass
?import?java.io.*; ?import?java.util.HashMap; ?import?java.util.Map; ?public?class?CustomerFindClass?extends?ClassLoader?{ ??private?Map<String,?String>?classPathMap?=?new?HashMap<>(); ??public?CustomerFindClass()?{ ???//?業(yè)務(wù)A的自定義類加載器 ???classPathMap.put("com.lgy.businessA.service.impl.MessageServiceImpl",?"E:/dataway-demo/example/target/classes/com/lgy/businessA/service/impl/MessageServiceImpl.class"); ???classPathMap.put("com.lgy.v1.message.util.MessageUtil",?"E:/dataway-demo/example/target/classes/com/lgy/v1/message/util/MessageUtil.class"); ??} ?? ??/** ??*?findClass方式加載類 ??*/ ??@Override ??protected?Class<?>?findClass(String?name)?throws?ClassNotFoundException?{ ???String?classPath?=?classPathMap.get(name); ???File?file?=?new?File(classPath); ???if?(!file.exists())?{ ????throw?new?ClassNotFoundException(); ???} ???byte[]?bytes?=?getClassData(file); ???if?(null?==?bytes?||?0?==?bytes.length)?{ ????throw?new?ClassNotFoundException(); ???} ???return?defineClass(bytes,?0,?bytes.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[]{}; ??}
最終結(jié)果與預(yù)期的結(jié)果不一致
- 預(yù)期結(jié)果:業(yè)務(wù)A的MessageServiceImpl與MessageUtil由CustomerFindClass加載
- 實際結(jié)果:業(yè)務(wù)A的MessageServiceImpl由CustomerFindClass加載,而MessageUtil由sun.misc.AppClassLoader加載。
- 分析:由于JVM類加載的雙親委托機制,業(yè)務(wù)A調(diào)用消息工具類時,類加載器(CustomerFindClass)會委托父類加載器(AppClassLoader)加載類,如果存在,則不再執(zhí)行自身的findClass方法加載,導(dǎo)致結(jié)果不理想。(main 方法類默認(rèn)情況下都是由 JDK 自帶的 AppClassLoader 加載的)。
重寫loadClass
?private?ClassLoader?classLoader; ? ?/** ?*?重新loadClass方法 ?*/ ?@Override ????protected?Class<?>?loadClass(String?name,?boolean?resolve)?throws?ClassNotFoundException?{ ????????Class?result?=?null; ????????try?{ ????????????//這里要使用?JDK?的類加載器加載?java.lang?包里面的類 ????????????result?=?classLoader.loadClass(name); ????????}?catch?(Exception?e)?{ ????????????//?ignore?error ????????} ????????if?(null?!=?result)?{ ????????????return?result; ????????} ????????String?classPath?=?classPathMap.get(name); ????????File?file?=?new?File(classPath); ????????if?(!file.exists())?{ ????????????throw?new?ClassNotFoundException(); ????????} ????????byte[]?bytes?=?getClassData(file); ????????if?(null?==?bytes?||?0?==?bytes.length)?{ ????????????throw?new?ClassNotFoundException(); ????????} ????????return?defineClass(bytes,?0,?bytes.length); ????}
滿足業(yè)務(wù)A的MessageServiceImpl與MessageUtil由CustomerFindClass加載
注意:這種方式破壞了雙親委托機制,但由于重寫了loadClass方法,所有類均會有CustomerFindClass加載器加載,需要過濾出不需要隔離的類,如java.lang包下的類,需要由ExtClassLoader 來加載。
總結(jié)
本文分享的方式是從類加載器方向出發(fā),實現(xiàn)最終的類隔離,避免了不同模塊間不同類的沖突,其中順便也簡單帶過了jvm類加載相關(guān)的知識點,也算是一勞多得,后續(xù)會結(jié)合實際使用場景進一步分析。
以上就是Java通過自定義類加載器實現(xiàn)類隔離的詳細(xì)內(nèi)容,更多關(guān)于Java類隔離的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Java實現(xiàn)數(shù)據(jù)結(jié)構(gòu)之并查集
并查集這種數(shù)據(jù)結(jié)構(gòu),可能出現(xiàn)的頻率不是那么高,但是還會經(jīng)常性的見到,其理解學(xué)習(xí)起來非常容易,通過本文,一定能夠輕輕松松搞定并查集2021-06-06java實現(xiàn)建造者模式(Builder Pattern)
這篇文章主要為大家詳細(xì)介紹了java實現(xiàn)建造者模式Builder Pattern,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-10-10詳解Spring DeferredResult異步操作使用場景
本文主要介紹了Spring DeferredResult異步操作使用場景,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10