链接在具有父子关系的类中加载错误



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

但是,我遇到了与使用的MemoryClassLoader类相关的java.lang.LinkageError问题。

/**
* 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);
}
}

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

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

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

我的总体问题是,如何在父子关系中相互关联的类中进行检测和加载,以使行为不受顺序的影响?我可以替换类的定义吗?另一个问题似乎是super.load,它将是MemoryClassLoader的父类加载器,我无法控制。

首先,您应该覆盖findClass而不是loadClass。具有特定名称的类只能在加载器中定义一次的限制始终适用,并且您可能始终遇到多次请求同一类的情况(在任何重要情况下)。

ClassLoader继承的loadClass实现已经处理了这个问题,并返回现有定义(如果有)。由于它还首先查询父加载器,因此如果加载器应该使用其他类装入器使用的名称定义类,则应注意指定正确的父装入器。使用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);
}
}

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

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

  2. 检测所有预期类的字节码并将结果放入加载器中,然后再对loadClass进行第一次调用。

    final MemoryClassLoader memoryClassLoader = new MemoryClassLoader();
    memoryClassLoader.addDefinition("MyClass", instrumentedMyClass);
    memoryClassLoader.addDefinition("MySubClass", instrumentedMySubClass);
    final Class<?> myClass = memoryClassLoader.loadClass("MyClass");
    

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

最新更新