如何使用 ClassLoader 隔离不同版本的 jar 包?

众所周知,因为classloader的隔离机制,一个jvm进程可以同时加载两个同名的class,但是如何使用这两个class呢。只能通过拿到class的引用,反射调用吗?

问题背景:系统A接入多家渠道,需要用到渠道的SDK,但是各个渠道的SDK都依赖了相同的加解密class,例如 org.apache.Cipher,但是版本都不一样。如何让这些SDK在一个JVM进程中无障碍使用。

场景和stackeroverflow这个问题一样:Java Classloader - how to reference different versions of a jar

阅读 3.4k
3 个回答

这时候 URLClassLoader 就派上用场了。下面是一个例子

public class ChannelCipherManager implements Closeable {

    private final Map<String, URLClassLoader> cipherClassLoaders = new HashMap<>();

    /**
     * 注册渠道自己的 cipher jar 包
     */
    public void registerCipherJar(String channel, File cipherJar) throws MalformedURLException {
        URLClassLoader cipherClassLoader = new URLClassLoader(new URL[]{cipherJar.toURI().toURL()});
        cipherClassLoaders.put(channel, cipherClassLoader);
    }

    /**
     * 根据渠道获得对应的 cipher 对象
     */
    @SuppressWarnings("unchecked")
    public Cipher getCipher(String channel) throws ClassNotFoundException {

        if (!cipherClassLoaders.containsKey(channel)) {
            // 或者注册一个默认的,找不到渠道就返回该默认值也行
            throw new IllegalArgumentException("Channel '" + channel + "' not registered");
        }

        Class<Cipher> cipherClass = (Class<Cipher>)
            cipherClassLoaders.get(channel).loadClass("org.apache.Cipher");

        return createCipherInstance(cipherClass);
    }

    /**
     * 创建 Cipher 实例对象
     */
    private Cipher createCipherInstance(Class<Cipher> cipherClass) {
        return null;  // TODO 这个交给你自己实现
    }

    /**
     * 根据需要释放资源
     */
    @Override
    public void close() throws IOException {
        this.cipherClassLoaders.values().forEach(cl -> {
            try {
                cl.close();
            } catch (IOException e) {
                // ignore error
            }
        });
    }
}

使用方法:

public static void main(String[] args) throws Exception {
    ChannelCipherManager ccm = new ChannelCipherManager();
    
    ccm.registerCipherJar("channel1", new File("lib-ch1/cipher.jar"));
    ccm.registerCipherJar("channel2", new File("lib-ch2/cipher.jar"));
    ccm.registerCipherJar("channel3", new File("lib-ch3/cipher.jar"));

    Cipher cipher = ccm.getCipher("channel2");
    // ...
}

如果你最终加密的结果,和版本无关,这你用哪个都没关系,如果是最终结果有差异,自定义classloader,加载外置jar也是可以的

这两个类的类加载器不一样,调用对应的类加载器就能获取到类

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题