我在使用Java类加载器时遇到了一些困难,也许有人可以对此有所了解。我已将问题的本质提取到以下内容:
有三个类 - ClassLoaderTest
,LoadedClass
和LoadedClassDep
。他们都在不同的道路上。
ClassLoaderTest
实例化一个新的URLClassLoader
- myClassLoader
,用其余两个类的路径引导它,并将它自己的类加载器(即应用程序类加载器)作为父类。然后,它使用Class.forName("com.example.LoadedClass", true, myClassLoader)
通过反射加载LoadedClass
。 LoadedClass
导入LoadedClassDep
。如果我运行上述内容,请使用:
java -cp /path/to/the/ClassLoaderTest ClassLoaderTest "/path/to/LoadedClass" "/path/to/LoadedClassDep"
并使用命令行参数来填充URLClassLoader
一切正常。使用静态初始化器我确认这两个类都加载了URLClassLoader
的实例。
但是,如果我这样做,那就是问题所在:
java -cp /path/to/the/ClassLoaderTest:/path/to/the/LoadedClass ClassLoaderTest "/path/to/LoadedClassDep"
无法加载LoadedClassDep(ClassNotFoundException
)。 LoadedClass
已正确加载,但使用sun.misc.Launcher$AppClassLoader
,而不是URLClassLoader
!
看来,由于应用程序类加载器能够加载LoadedClass
,它还会尝试加载LoadedClassDep
,而忽略URLClassLoader
。
这是完整的源代码:
package example.bc;
public class ClassloaderTest {
public static void main(String[] args) {
new ClassloaderTest().run(args);
}
private void run(String[] args) {
URLClassLoader myClasLoader = initClassLoader(args);
try {
Class<?> cls = Class.forName("com.example.bc.LoadedClass", true, myClasLoader);
Object obj = cls.newInstance();
cls.getMethod("call").invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
private URLClassLoader initClassLoader(String[] args) {
URL[] urls = new URL[args.length];
try {
for (int i = 0; i < args.length; i++) {
urls[i] = new File(args[i]).toURI().toURL();
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
return new URLClassLoader(urls, getClass().getClassLoader());
}
}
package com.example.bc;
import com.bc.LoadedClassDep;
public class LoadedClass {
static {
System.out.println("LoadedClass " + LoadedClass.class.getClassLoader().getClass());
}
public void call() {
new LoadedClassDep();
}
}
package com.bc;
public class LoadedClassDep {
static {
System.out.println("LoadedClassDep " + LoadedClassDep.class.getClassLoader().getClass());
}
}
我希望我说得够清楚。我的问题是,我只知道编译时ClassLoadeTest
的路径,我必须在运行时为其他路径使用字符串。那么,任何想法如何使第二个场景有效?
答案 0 :(得分:1)
我希望应用程序类加载器在第二种情况下加载LoadedClass
,因为类加载器最初委托给它们的父级 - 这是standard behaviour。在第二种情况下,LoadedClass
位于父类的路径上,因此它加载类而不是放弃并让URLClassLoader
尝试。
然后,应用程序类加载器会尝试加载LoadedClassDep
,因为它是在LoadedClass
中直接导入和引用的:
public void call() {
new LoadedClassDep();
}
如果需要在运行时动态且独立地加载这些类,则不能以这种方式直接引用它们。
也可以更改类加载器的尝试顺序 - 有关此问题的讨论,请参阅Java classloaders: why search the parent classloader first?。