我做了一些测试,我很确定规范是否正确实施.我的错误在于反思性地加载类与将其作为解析步骤的一部分加载相同.这是有道理的:规范和JavaDoc都提到了类加载器的“记录”作为启动类加载器.如果我自己调用loadClass(),VM无法知道哪个类加载器应该是启动类加载器,因此定义的类加载器通常也会成为启动类加载器.
这可以通过将加载的类触发加载另一个类(foo.Baz)作为依赖项解析的一部分但是让另一个类加载器执行实际加载来证明.*
*我很确定这不是有效类加载器的正确行为.我这样做是为了说明一点.
考虑以下类(它们都在包foo中):
public class Bar {
public Bar() {
new Baz();
}
}
和
public class Baz {
}
我的自定义类加载器现在略有修改:
public class SimpleClassLoader extends ClassLoader {
static final String PATH = "/path/to/classes";
public SimpleClassLoader() {
// disable parent delegation
super(null);
}
public void printLoadedClass(String name) throws Exception {
Class> cls = findLoadedClass(name);
System.err.println("findLoadedClass(" + name + ") = " + cls
+ ", has class loader " + cls.getClassLoader());
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
if (name.equals("foo.Baz")) {
// don't want to be defining class loader of foo.Baz
return getSystemClassLoader().loadClass(name);
}
// now we're loading foo.Bar
try {
byte[] b = IOUtils.toByteArray(new FileInputStream(PATH + "/foo/Bar.class"));
return defineClass(name, b, 0, b.length);
} catch (ClassFormatError | IOException e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
测试很简单:
public static void main(String[] args) throws Exception {
SimpleClassLoader cl = new SimpleClassLoader();
Class> cls = cl.loadClass("foo.Bar");
cls.newInstance(); // this triggers resolution
cl.printLoadedClass("foo.Bar");
cl.printLoadedClass("foo.Baz");
}
输出是
findLoadedClass(foo.Bar) = class foo.Bar, has class loader foo.SimpleClassLoader@3a65724d
findLoadedClass(foo.Baz) = class foo.Baz, has class loader sun.misc.Launcher$AppClassLoader@1a2b2cf8
可以看出:SimpleClassLoader启动加载并定义foo.Bar.创建实例会触发foo.Baz的解析.这次,类的定义被委托给了系统类加载器,因此它成为定义类加载器.输出显示SimpleClassLoader正在为两个类启动类加载器,但仅定义第一个类.