在 类 中加载具有父子关系的 LinkageError

LinkageError from loading in classes that have a parent-child relation

我按照 http://www.jacoco.org/jacoco/trunk/doc/examples/java/CoreTutorial.java 处的 CoreTutorial 示例 class 了解如何将 Jacoco 合并到项目中。

但是,我面临 java.lang.LinkageError 与所使用的 MemoryClassLoader class 相关的问题。

/**
 * A class loader that loads classes from in-memory data.
 */
public static class MemoryClassLoader extends ClassLoader {

    private final Map<String, byte[]> definitions = new HashMap<String, byte[]>();

    /**
     * Add a in-memory representation of a class.
     * 
     * @param name
     *            name of the class
     * @param bytes
     *            class definition
     */
    public void addDefinition(final String name, final byte[] bytes) {
        definitions.put(name, bytes);
    }

    @Override
    protected Class<?> loadClass(final String name, final boolean resolve)
            throws ClassNotFoundException {
        final byte[] bytes = definitions.get(name);
        if (bytes != null) {
            return defineClass(name, bytes, 0, bytes.length);
        }
        return super.loadClass(name, resolve);
    }

}

具体来说,我有两个 class 类,MyClass 和 MySubClass,其中 MySubClass 扩展了 MyClass。我检测了两个 classes,以便我可以获得与每个相关的覆盖信息。我发现,如果我首先检测 MySubClass,MemoryClassLoader 将使用 MySubClass 的检测字节调用 defineClass。但是,这样做时,作为父 class 的 MyClass 也会被父 class 加载器加载。因此,当我下次使用并加载 MyClass 时,我收到 java.lang.LinkageError,它声称已经有一个由 ClassLoader 加载的 MyClass 的定义。这里的问题是未检测加载的 MyClass 的初始版本。

如果我以相反的顺序加载 classes,则不会出现此问题。

我在这里尝试了一些不同的东西:https://github.com/huangwaylon/randoop/blob/bloodhound/src/main/java/randoop/main/MemoryClassLoader.java 通过在递归调用 loadClass 时立即尝试检测父 class。但是,由于我尚未弄清楚的原因,这并不完全有效。我观察到如果我使用这种错误的方法,两个 classes 的覆盖率信息都不会改变。

我的总体问题是,如何检测和加载在父子关系中彼此相关的 classes,以便行为不受顺序影响?我可以替换 class 的定义吗?另一个问题似乎是 super.load 这将是我无法控制的 MemoryClassLoader 的父 ClassLoader。

首先,您应该覆盖 findClass 而不是 loadClass。具有特定名称的 class 只能在加载程序中定义一次的限制 总是 适用,您可能总是会遇到请求相同 class 的情况多次(在任何重要的场景中)。

ClassLoader 继承的 loadClass 实现已经处理了这一点,并且 return 现有定义(如果有)。因为它也首先查询父加载器,你应该注意指定正确的父加载器,如果加载器应该定义 classes 的名称也被其他 class 加载器使用。使用 loadClass 的默认实现,您的 findClass 方法可以保持如此简单:

public static class MemoryClassLoader extends ClassLoader {
    private final Map<String, byte[]> definitions = new HashMap<>();

    public void addDefinition(final String name, final byte[] bytes) {
        definitions.put(name, bytes);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        final byte[] bytes = definitions.get(name);
        if (bytes != null) {
            return defineClass(name, bytes, 0, bytes.length);
        }
        return super.findClass(name);
    }
}

可以通过两种方式确保所有预期的 classes 都得到检测。

  1. 按需检测它们,即让 findClass 方法触发所请求的检测 class.

  2. 在第一次调用 loadClass.

    之前检测所有预期的 classes 的字节码并将结果放入加载程序
    final MemoryClassLoader memoryClassLoader = new MemoryClassLoader();
    memoryClassLoader.addDefinition("MyClass", instrumentedMyClass);
    memoryClassLoader.addDefinition("MySubClass", instrumentedMySubClass);
    final Class<?> myClass = memoryClassLoader.loadClass("MyClass");
    

    在这里,addDefinition 调用的顺序无关紧要,先调用 loadClass("MyClass") 还是先调用 loadClass("MySubClass") 也无关紧要。这甚至适用于循环依赖。