前言
由于微服務的快速迭代、持續(xù)集成等特性,越來越多的團隊更傾向于它。但是也體現(xiàn)出了一些問題,比如在基礎設施建設過程中,需要把通用功能下沉,把現(xiàn)有大而全的基礎設施按領域拆分,考慮需要兼容現(xiàn)有生產(chǎn)服務,會產(chǎn)生不同的依賴版本,有時不注意就可以引發(fā)問題。比如本文遇到的依賴包版本沖突問題,以及如何利用類隔離技術解決的分析。
類隔離是什么?
類隔離是一種通過類加載器實現(xiàn)加載所需類的實現(xiàn)方式,使得不同版本類間隔離,避免了使用沖突問題,最終的效果就是不同模塊的內(nèi)容被不同的類加載器加載,滿足同一環(huán)境下同時兼容不同接口實現(xiàn)類。
使用場景
比如業(yè)務服務A和業(yè)務服務B均需要消息通知等,均依賴消息中間件,但所引用版本不一致,導致最終只有一個版本加載到JVM,在某一個服務調(diào)用時會出現(xiàn) NoSuchMethodError或NoSuchClassError問題,這就很難排查出來,沒準會影響項目進度,最終月度的績效(“雞腿”)不保。
服務A pom.xml:
< !-- common-message-- >
< dependency >
< groupId >com.lgy< /groupId >
< artifactId >spring-common-message< /artifactId >
< version >1.0.0< version >
< /dependency >
服務B pom.xml:
< !-- common-message-- >
< dependency >
< groupId >com.lgy< /groupId >
< artifactId >spring-common-message< /artifactId >
< version >2.0.0< version >
< /dependency >
業(yè)務調(diào)用流程:
// 業(yè)務A調(diào)用微信服務通知
MessageUtil.sendMessage(content,peopleId,templateId,"wechat");
// 業(yè)務B調(diào)用微信服務通知
MessageUtil.sendToWechat(content,peopleId,templateId);
JVM最終加載的為 2.0.0 版本的依賴,導致業(yè)務A在調(diào)用時拋異常java.lang.NoSuchMethodError。
解決方案
大體的解決思路就是,在不改變業(yè)務代碼的前提下, 業(yè)務A調(diào)用 1.0.0 版本的消息工具類, 業(yè)務B調(diào)用2.0.0版本的消息工具類,因此需要JVM能夠利用自定義類加載器加載所需的類或關聯(lián)的類。
實現(xiàn)思路
- 重寫類加載器,實現(xiàn)自定義類加載(java.lang.ClassLoader)
- 重寫類加載函數(shù)
涉及的知識點
- 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è)務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[]{};
}
最終結果與預期的結果不一致
- 預期結果:業(yè)務A的MessageServiceImpl與MessageUtil由CustomerFindClass加載
- 實際結果:業(yè)務A的MessageServiceImpl由CustomerFindClass加載,而MessageUtil由sun.misc.AppClassLoader加載。
- 分析:由于JVM類加載的雙親委托機制,業(yè)務A調(diào)用消息工具類時,類加載器(CustomerFindClass)會委托父類加載器(AppClassLoader)加載類,如果存在,則不再執(zhí)行自身的findClass方法加載,導致結果不理想。(main 方法類默認情況下都是由 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è)務A的MessageServiceImpl與MessageUtil由CustomerFindClass加載
注意:這種方式破壞了雙親委托機制,但由于重寫了loadClass方法,所有類均會有CustomerFindClass加載器加載,需要過濾出不需要隔離的類,如java.lang包下的類,需要由ExtClassLoader 來加載。
總結
本文分享的方式是從類加載器方向出發(fā),實現(xiàn)最終的類隔離,避免了不同模塊間不同類的沖突,其中順便也簡單帶過了jvm類加載相關的知識點,也算是一勞多得,后續(xù)會結合實際使用場景進一步分析。
-
接口
+關注
關注
33文章
8712瀏覽量
151998 -
函數(shù)
+關注
關注
3文章
4346瀏覽量
63006 -
微服務
+關注
關注
0文章
143瀏覽量
7443 -
類加載器
+關注
關注
0文章
6瀏覽量
941
發(fā)布評論請先 登錄
相關推薦
評論