【问题标题】:classloader: how to load different version of jar类加载器:如何加载不同版本的 jar
【发布时间】:2018-03-14 05:08:43
【问题描述】:

我必须使用第 3 方平台,但该平台有无法替换的旧版本 jar libjar-1.0.0.jar。该平台让我在其上运行我自己的(平面文件)包。我将新版本的libjar-2.0.0.jar 放在我的包/packages/package-name/external-jar 下。当我使用URLClassLoader 加载libjar-2.0.0.jar 然后打印出所有声明的方法时,我能够看到2.0.0 jar 中的方法。但是,当我调用时,我总是得到NoSuchMethodException。当我打印出 newobj.class.getProtectionDomain().getCodeSource().getLocation().toString() 时,它总是显示 libjar-1.0.0.jar 。谁能帮助解释我做错了什么以及在运行时强制使用特定 jar 中的类需要做什么?

这是我的代码快照

File f = new File(path);

URL[] urls = new URL[1];
urls[0] = f.toURI().toURL();
ClassLoader cl = new URLClassLoader(urls);

Class<?> utilsClass = cl.loadClass("com.myclass");
Constructor<?> cons = utilsClass.getConstructor(First.class, Second.class);
Object utils = cons.newInstance(firstObj, new Second());
if (utilsClass.getProtectionDomain() != null) {
           LOGGER.info(utilsClass.getProtectionDomain().getCodeSource().getLocation().toString());
}
// this print out --- 1.0.0.jar instead of 2.0.0.jar

for (Method m : utilsClass.getDeclaredMethods()) {
     LOGGER.info("methods: " + m.getName());
}
// method shows the "methodILookFor"

Method m = utilsClass.getDeclaredMethod("methodILookFor", Target.class, String[].class, Object.class);
// always throws NoSuchMethodException

m.invoke(utils, target, string, obj);

【问题讨论】:

  • 您是否使用 Maven 来构建您的项目?
  • 没有。这是来自第 3 方平台的 make 文件。

标签: java classloader


【解决方案1】:

您可以尝试使用自定义类加载器来使用其完全限定名称加载类,而不是使用 URL 类加载器。使用这种方法,您应该能够绕过类加载委托给父类加载器,这在您的情况下会导致问题。所以你的类加载器应该能够从 libjar-2.0.0.jar 加载类。

【讨论】:

  • 请原谅我缺乏知识。什么是 fqn?
【解决方案2】:

类加载的工作原理

  • URLClassLoader 用于加载尚未在应用程序类路径中指定的类。
    • 类加载基于委托原则。如果一个类没有被加载,加载这个类的任务由类加载器委托给它的父类加载器。如果父类加载器没有找到该类,则将其传递给子类加载器进行加载。
    • 在您的情况下,URLClassLoader 将类加载委托给其父级,即Application Class Loader
    • Application Class Loaderlibjar-1.0.0.jar 中找到类。因此,URLClassLoader 最终不会从 libjar-2.0.0.jar 加载类。

自定义类加载器

这是一个扩展 URLClassLoader 的自定义类加载器的简单示例。此类加载器在委托给其父类加载器之前尝试从其 URL 加载类。它应该能够加载您在示例中需要的不同版本的 JAR。您将找到带有单元测试的完整示例 here

P.S. Java 9 中的类加载已更改。它未经 Java 9 测试,可能无法正常工作。

public class MyClassLoader extends URLClassLoader {

    public MyClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    protected synchronized Class<?> loadClass(String name,
            boolean resolve) throws ClassNotFoundException {

        // 1. Check if the class has already been loaded
        Class<?> clazz = findLoadedClass(name);

        ClassLoader parentCL = getParent();

        // 2. If the class is not loaded and the class name starts
        // with 'java.' or 'javax.', delegate loading to parent
        if (clazz == null && parentCL != null && (name.startsWith(
                "java.") || name.startsWith(
                "javax."))) {
            clazz = parentCL.loadClass(name);

        }

        // 3. If the class is still null, try to load the class from the URL
        // (since we have already taken care of 'java.' and 'javax.'
        if (clazz == null) {
            try {
                clazz = super.findClass(name);
            } catch (ClassNotFoundException e) {
                //don't do anything
            }
        }

        // 4. If the class is still null, let the parent class loader load it.
        // Previously, we allowed 'java.' and 'javax.' classes to be loaded
        // from parent
        if (clazz == null && parentCL != null) {
            clazz = parentCL.loadClass(name);
        }

        // 5. If the class is still null, throw a class not found exception
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }

        if (resolve) {
            resolveClass(clazz);
        }

        return clazz;
    }
}

【讨论】:

  • 感谢您的解释。有没有办法强制将特定的罐子加载到另一个罐子上?
  • 在大多数情况下,这不是微不足道的。 OSGi 框架有一个很好的方法来解决类路径地狱问题。 Java 9 模块化是朝着这个方向迈出的一步。您是否可以将版本 2 JAR 放在应用程序类路径中的第 3 方 JAR 之前。如果这不可行,请解压第 3 方 JAR 并将 1.0 版 JAR 替换为 2.0 JAR,然后将 JAR 升级为第 3 方 JAR。
  • 谢谢!似乎第 3 方平台正在处理类路径等。我需要找到一种方法来找到它在哪里加载 jar。这不是一个直观的平台,但我别无选择。当我将 jar 替换为较新版本的 jar 时,平台崩溃了。与供应商交谈,他们没有计划升级到较新的 jar。但是,我们的开发需要新 jar 中的新方法/功能。我不确定 unjar -> 带有 2.0 jar 的 jar 在这种情况下也可以工作。
  • 您使用的是什么第三方平台?
  • 1. 如果您使用自定义类加载器,您应该将最新的 jar 保存在其他位置,这样它就不会被应用程序类加载器拾取。 2. 使用自定义类加载器指定version 2 jar 的位置。 3. 每次您想在version 2 jar 中创建一个类的新实例时,您都必须使用自定义类加载器。 4. 有一个way 用自定义类加载器替换系统类加载器,但你必须小心。
猜你喜欢
  • 2013-01-14
  • 2012-07-30
  • 1970-01-01
  • 1970-01-01
  • 2010-09-13
  • 1970-01-01
  • 2017-11-01
  • 1970-01-01
相关资源
最近更新 更多