同一个服务需要使用同一个依赖(jar)的不同版本的解决方案(类加载器方案)
当使用类加载器隔离来处理同一依赖的不同版本时,可以创建自定义的类加载器来加载各自的版本。以下是一个简单的示例,演示如何使用类加载器隔离不同版本的依赖:
// 自定义类加载器
public class CustomResourcesClassLoader extends ClassLoader {
private File jarPath;
public CustomResourcesClassLoader(File jarPath, ClassLoader parent) {
super(parent);
this.jarPath = jarPath;
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
try {
// 从指定路径加载类字节码
byte[] classBytes = loadClassBytes(className);
return defineClass(className, classBytes, 0, classBytes.length);
} catch (IOException e) {
throw new ClassNotFoundException("Failed to load class: " + className, e);
}
}
private byte[] loadClassBytes(String className) throws IOException {
if("org.bouncycastle.math.ec.LongArray".equals(className)){
System.out.println("");
}
try (JarFile jarFile = new JarFile(this.jarPath)) {
JarEntry jarEntry = jarFile.getJarEntry(className.replace('.', '/') + ".class");
if (jarEntry != null) {
try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
return getBuff(inputStream);
}
}else{
System.out.println(className);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private byte[] getBuff(InputStream inputStream){
try {
// 读取数据缓冲区
byte[] buffer = new byte[1024];
int bytesRead;
// 使用 ByteArrayOutputStream 存储读取的数据
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 循环读取数据
while ((bytesRead = inputStream.read(buffer)) != -1) {
// 将读取到的数据写入 ByteArrayOutputStream
outputStream.write(buffer, 0, bytesRead);
}
// 获取读取到的所有数据
byte[] data = outputStream.toByteArray();
outputStream.close();
return data;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
// 关闭输入流
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在上面的示例中,CustomClassLoader是自定义的类加载器,它继承自ClassLoader。在findClass方法中,通过指定的路径加载对应类的字节码。您可以根据实际需求自定义加载逻辑,例如从不同的JAR包中加载字节码。
在MyMicroservice微服务类中,创建了两个不同的类加载器,分别用于加载不同版本的依赖。然后,使用各自的类加载器加载对应的类,并通过反射调用方法。
请注意,这只是一个简单的示例,实际场景中可能需要更复杂的类加载器逻辑
注意事项:
- 一些涉及到加密、安全相关的jar包,只能通过默认的加载器方式加载,比如bcprov-jdk18on之类的jar包,只能通过如下方式加载
URLClassLoader classLoader173 = new URLClassLoader(new URL[]{getUrl(bcprovFileName)},
ClassLoader.getSystemClassLoader());
classBouncyCastleProvider = classLoader173.loadClass(
"org.bouncycastle.jce.provider.BouncyCastleProvider");
- 因为双亲委派的特性,类加载器首先是交给父类加载器进行加载,只有当父类加载器无法加载的时候才交给子类加载器加载,因此为了真正的隔离两个不同版本的依赖,必须保证两个依赖使用的类加载器同级,同时最好父类加载器为null
public class CustomResourcesClassLoader extends ClassLoader {
private File jarPath;
public CustomResourcesClassLoader(File jarPath, ClassLoader parent) {
super(null);
this.jarPath = jarPath;
}
}