我正在尝试使用标记-Djava.system.class.loader=MyLoader
覆盖系统的类加载器。但是,加载类时仍未使用MyLoader
。
MyLoader
的代码:
public class MyLoader extends ClassLoader {
public MyLoader(ClassLoader parent) {
super(S(parent));
}
private static ClassLoader S(ClassLoader cl) {
System.out.println("---MyLoader--- inside #constructor(" + cl + ")...");
return cl;
}
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
System.out.println("---MyLoader--- inside loadClass(" + name + ", " + resolve + ")...");
return super.loadClass(name, resolve);
}
}
这是主要代码:
public class Main {
public static void main(final String args[]) throws Exception {
System.out.println("---Main--- first line");
System.out.println("---Main--- getSystemClassLoader(): " + ClassLoader.getSystemClassLoader());
System.out.println("---Main--- getSystemClassLoader()'s loader: " + ClassLoader.getSystemClassLoader().getClass().getClassLoader());
Call("javax.crypto.Cipher");
}
public static void Call(final String class_name) throws Exception {
System.out.println("---Main--- calling Class.forName(" + class_name + ")...");
Class.forName(class_name);
System.out.println("---Main--- call complete");
}
}
这是使用命令java -Djava.system.class.loader=MyLoader -verbose -Xshare:off
(参见Eclipse run config)输出的输出:
[已打开C:\ Program Files \ Java \ jre7 \ lib \ rt.jar]
[从C:\ Program Files \ Java \ jre7 \ lib \ rt.jar加载java.lang.Object]
[从C:\ Program加载java.io.Serializable 文件\爪哇\ jre7 \ lib中\ rt.jar中]
//等等...因为它太长而省略了
[加载MyLoader来自 文件:/ C:/文件%20于是%20Settings /所有者/桌面/程序/ Eclipse的%20Workspace%202 / Test93 / bin中/
--- MyLoader --- 在#constructor内(sun.misc.Launcher$AppClassLoader@158046e) ......
[加载了来自C:\ Program的sun.launcher.LauncherHelper 文件\爪哇\ jre7 \ lib中\ rt.jar中]
[从C:\ Program加载java.lang.StringCoding 文件\爪哇\ jre7 \ lib中\ rt.jar中]
[从C:\ Program加载java.lang.StringCoding $ StringDecoder 文件\爪哇\ jre7 \ lib中\ rt.jar中]
--- MyLoader --- 里面的loadClass(Main,false) ......
[从文件加载主文件:/ C:/ Documents%20和%20Settings / Owner / Desktop / Programs / Eclipse%20Workspace%202 / Test93 / bin /]
[从C:\ Program Files \ Java \ jre7 \ lib \ rt.jar加载java.lang.Void]
---主要---第一行
--- Main --- getSystemClassLoader():MyLoader @ 8697ce
--- Main --- getSystemClassLoader()的加载器:sun.misc.Launcher$AppClassLoader@158046e
--- Main --- 调用Class.forName(javax.crypto.Cipher) ...
[已打开C:\ Program Files \ Java \ jre7 \ lib \ jce.jar]
[从C:\ Program加载javax.crypto.Cipher 文件\爪哇\ jre7 \ lib中\ jce.jar]
---主要--- 致电完成
可以看出,即使使用Main
加载了MyLoader
,也未使用MyLoader
加载javax.crypto.Cipher
。输出显示MyLoader.loadClass
仅被调用一次。
从jce.jar加载MyLoader.loadClass
时,为什么javax.crypto.Cipher
甚至没有调用?
答案 0 :(得分:9)
您的问题是您的自定义类加载器用于加载Main,但其loadClass只是委托给父类加载器来加载Main。因此。在Main中,如果您拨打Main.class.getClassLoader()
,则会返回sun.misc.Launcher$AppClassLoader
,而不是 MyLoader
。
要查看哪个类加载器将用于Class.forName(String)
调用以及来自您自己的类的符号引用,您应该从静态方法打印getClass().getClassLoader()
(或MyClass.class.getClassLoader()
)。使用的类加载器是定义当前正在执行其代码的类的类加载器。这是除了使用反射(Class.forName(String, boolean, ClassLoader)
)之外的所有规则。
从父类加载器加载一个类后,它加载的任何类也将使用该基本类加载器。因此,一旦从sun.misc.Launcher$AppClassLoader
类加载器加载Main,它调用的所有类将来自同一个类加载器,不来自您自己的MyLoader
。类似地,一旦从null(也称为Bootstrap)类加载器加载javax.crypto.Cypher
类,它提到的任何类也将来自引导类加载器,除了它使用反射(SPI)加载的类。
要停止从sun.misc.Launcher$AppClassLoader
类加载器加载类,请将MyLoader
的CLASSPATH设置为AppClassLoader
的CLASSPATH,并且不要将类加载委托给AppClassLoader
。请注意,这将导致从MyLoader加载所有CLASSPATH类,但JDK中的类通常仍将从null(Bootstrap)类加载器加载。
要停止从引导类加载器加载JDK类,必须将JDK显式放入类路径并修改loadClass,以便不首先检查某些类的父级。从您自己的类加载器加载JDK类很精细;必须从boostrap类加载器加载某些类(例如java.lang.String) 。这不是我自己尝试过的,但我已经读过OSGi从引导类加载器加载java。*但从其自己的类加载器图中加载其他JDK类(例如sun。*和javax。*)
/** Run with -Djava.system.class.loader=MyLoader to use this class loader. */
public static class MyLoader extends URLClassLoader {
public MyLoader(ClassLoader launcherClassLoader) {
super(getUrls(launcherClassLoader), launcherClassLoader.getParent());
}
private static URL[] getUrls(ClassLoader cl) {
System.out.println("---MyLoader--- inside #constructor(" + cl + ")...");
return ((URLClassLoader) cl).getURLs();
}
@Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
System.out.println("---MyLoader--- inside loadClass(" + name + ", " + resolve + ")...");
return super.loadClass(name, resolve);
}
}
对于JDK中的SPI工厂(想想XML解析器和加密实现),他们使用反射从ContextClassLoader或SystemClassLoader或者一个接一个地加载命名类,因为他们希望你能够定义自己的实现,并且引导类加载器不加载用户定义的类。他们使用的两个中的哪一个似乎没有一致性,我希望他们只使用ClassLoader参数而不是猜测。
答案 1 :(得分:6)
Class.forName(String)州的Javadoc(强调我的):
返回与具有给定字符串名称的类或接口关联的Class对象。调用此方法相当于: Class.forName(className,true,currentLoader) 其中currentLoader表示当前类的定义类加载器。
换句话说,该方法不会自动使用系统类加载器 - 它使用物理定义它所调用的类的加载器。来自Java language spec, section 5.3:
类加载器L可以通过直接定义C或通过委托给另一个类加载器来创建C.如果L直接创建C,我们说L定义C,或者等价地说L是C的定义加载器。
您的自定义类加载器不会直接创建Main
类 - 它会委托父加载器来创建类,因此当您调用{{{}时,它将使用父类加载器。 1}}在Class.forName(String)
的方法中。如果您想直接使用自定义类加载器,则需要使用Main
的3-arg变量显式指定它,或者更改自定义类加载器实现,以便它实际加载有问题的类(通常通过扩展Class.forName
)。