同一个服务需要使用同一个依赖(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微服务类中,创建了两个不同的类加载器,分别用于加载不同版本的依赖。然后,使用各自的类加载器加载对应的类,并通过反射调用方法。

请注意,这只是一个简单的示例,实际场景中可能需要更复杂的类加载器逻辑

注意事项:

  1. 一些涉及到加密、安全相关的jar包,只能通过默认的加载器方式加载,比如bcprov-jdk18on之类的jar包,只能通过如下方式加载
URLClassLoader classLoader173 = new URLClassLoader(new URL[]{getUrl(bcprovFileName)},
                    ClassLoader.getSystemClassLoader());

            classBouncyCastleProvider = classLoader173.loadClass(
                    "org.bouncycastle.jce.provider.BouncyCastleProvider"); 
  1. 因为双亲委派的特性,类加载器首先是交给父类加载器进行加载,只有当父类加载器无法加载的时候才交给子类加载器加载,因此为了真正的隔离两个不同版本的依赖,必须保证两个依赖使用的类加载器同级,同时最好父类加载器为null
    public class CustomResourcesClassLoader extends ClassLoader {
    private File jarPath;

    public CustomResourcesClassLoader(File jarPath, ClassLoader parent) {
        super(null);
        this.jarPath = jarPath;
    }
}
posted @ 2023-06-08 17:41  镇魂帆-张  阅读(294)  评论(0编辑  收藏  举报