前言
由于微服務(wù)的快速迭代、持續(xù)集成等特性,越來(lái)越多的團(tuán)隊(duì)更傾向于它。但是也體現(xiàn)出了一些問(wèn)題,比如在基礎(chǔ)設(shè)施建設(shè)過(guò)程中,需要把通用功能下沉,把現(xiàn)有大而全的基礎(chǔ)設(shè)施按領(lǐng)域拆分,考慮需要兼容現(xiàn)有生產(chǎn)服務(wù),會(huì)產(chǎn)生不同的依賴版本,有時(shí)不注意就可以引發(fā)問(wèn)題。比如本文遇到的依賴包版本沖突問(wèn)題,以及如何利用類隔離技術(shù)解決的分析。
類隔離是什么?
類隔離是一種通過(guò)類加載器實(shí)現(xiàn)加載所需類的實(shí)現(xiàn)方式,使得不同版本類間隔離,避免了使用沖突問(wèn)題,最終的效果就是不同模塊的內(nèi)容被不同的類加載器加載,滿足同一環(huán)境下同時(shí)兼容不同接口實(shí)現(xiàn)類。
使用場(chǎng)景
比如業(yè)務(wù)服務(wù)A和業(yè)務(wù)服務(wù)B均需要消息通知等,均依賴消息中間件,但所引用版本不一致,導(dǎo)致最終只有一個(gè)版本加載到JVM,在某一個(gè)服務(wù)調(diào)用時(shí)會(huì)出現(xiàn) NoSuchMethodError或NoSuchClassError問(wèn)題,這就很難排查出來(lái),沒(méi)準(zhǔn)會(huì)影響項(xiàng)目進(jìn)度,最終月度的績(jī)效(“雞腿”)不保。
服務(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)用時(shí)拋異常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)的類。
實(shí)現(xiàn)思路
- 重寫類加載器,實(shí)現(xiàn)自定義類加載(java.lang.ClassLoader)
- 重寫類加載函數(shù)
涉及的知識(shí)點(diǎn)
- JVM加載過(guò)程:加載-》鏈接-》初始化(具體后續(xù)介紹)
- 雙親委派機(jī)制:委托父加載器查詢;如果父加載器查詢不到,則調(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加載
- 實(shí)際結(jié)果:業(yè)務(wù)A的MessageServiceImpl由CustomerFindClass加載,而MessageUtil由sun.misc.AppClassLoader加載。
- 分析:由于JVM類加載的雙親委托機(jī)制,業(yè)務(wù)A調(diào)用消息工具類時(shí),類加載器(CustomerFindClass)會(huì)委托父類加載器(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加載
注意:這種方式破壞了雙親委托機(jī)制,但由于重寫了loadClass方法,所有類均會(huì)有CustomerFindClass加載器加載,需要過(guò)濾出不需要隔離的類,如java.lang包下的類,需要由ExtClassLoader 來(lái)加載。
總結(jié)
本文分享的方式是從類加載器方向出發(fā),實(shí)現(xiàn)最終的類隔離,避免了不同模塊間不同類的沖突,其中順便也簡(jiǎn)單帶過(guò)了jvm類加載相關(guān)的知識(shí)點(diǎn),也算是一勞多得,后續(xù)會(huì)結(jié)合實(shí)際使用場(chǎng)景進(jìn)一步分析。
-
接口
+關(guān)注
關(guān)注
33文章
8447瀏覽量
150724 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4283瀏覽量
62325 -
微服務(wù)
+關(guān)注
關(guān)注
0文章
131瀏覽量
7322 -
類加載器
+關(guān)注
關(guān)注
0文章
6瀏覽量
922
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論