我正在尝试使用标志覆盖系统的类加载器-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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是主要代码:
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");
}
}
Run Code Online (Sandbox Code Playgroud)
这是使用命令的输出(参见Eclipse运行配置):java -Djava.system.class.loader=MyLoader -verbose -Xshare:off
[已打开C:\ Program Files\Java\jre7\lib\rt.jar]
[从C:\ Program Files\Java\jre7\lib\rt.jar加载java.lang.Object]
[从C:\ Program Files\Java\jre7\lib\rt.jar加载java.io.Serializable]
//等等...省略,因为它太长了
[从文件中加载MyLoader:/ C:/ Documents%20和%20Settings/Owner/Desktop/Programs/Eclipse%20Workspace%202/Test93/bin /]
--- MyLoader --- 在#constructor里面(sun.misc.Launcher$AppClassLoader@158046e) ......
[从C:\ Program Files\Java\jre7\lib\rt.jar加载sun.launcher.LauncherHelper]
[从C:\ Program Files\Java\jre7\lib\rt.jar加载java.lang.StringCoding]
[从C:\ Program Files\Java\jre7\lib\rt.jar加载java.lang.StringCoding $ StringDecoder]
--- 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 Files\Java\jre7\lib\jce.jar加载javax.crypto.Cipher]
---主要--- 通话完成
可以看出,即使Main加载使用MyLoader,javax.crypto.Cipher也不加载使用MyLoader.输出显示MyLoader.loadClass只调用一次.
从jce.jar加载时为什么MyLoader.loadClass甚至不调用javax.crypto.Cipher?
yon*_*ran 10
您的问题是您的自定义类加载器用于加载Main,但其loadClass只是委托父类加载器加载Main.因此.在Main中,如果你打电话Main.class.getClassLoader(),它会返回sun.misc.Launcher$AppClassLoader,而不是 MyLoader.
要查看哪个类加载器将用于Class.forName(String)来自您自己的类的调用和符号引用,您应该打印getClass().getClassLoader()(或MyClass.class.getClassLoader()从静态方法).使用的类加载器是定义当前正在执行其代码的类的类加载器.这是除了使用reflection(Class.forName(String, boolean, ClassLoader))之外的所有规则.
从父类加载器加载一个类后,它加载的任何类也将使用该基本类加载器.因此,一旦从sun.misc.Launcher$AppClassLoader类加载器加载Main ,它调用的所有类将来自同一个类加载器,而不是来自您自己的类加载器MyLoader.类似地,一旦javax.crypto.Cypher从null(也称为Bootstrap)类加载器加载类,它提到的任何类也将来自引导类加载器,除了它使用反射(SPI)加载的类.
要停止从sun.misc.Launcher$AppClassLoader类加载器加载类,请将MyLoaderCLASSPATH 设置为AppClassLoaderCLASSPATH,并且不要将类加载委托给AppClassLoader.请注意,这将导致从MyLoader加载所有CLASSPATH类,但JDK中的类通常仍将从null(Bootstrap)类加载器加载.
要停止从引导类加载器加载JDK类,必须将JDK显式放入类路径并修改loadClass,以便不检查某些类的父级.从您自己的类加载器加载JDK类很精细; 必须从boostrap类加载器加载一些类(例如java.lang.String).这不是我自己尝试过的,但我读过OSGi从bootstrap类加载器加载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);
}
}
Run Code Online (Sandbox Code Playgroud)
对于JDK中的SPI工厂(想想XML解析器和加密实现),他们使用反射从ContextClassLoader或SystemClassLoader或者一个接一个地加载命名类,因为他们希望您能够定义自己的实现,并且引导类加载器不加载用户定义的类.他们使用的两个中的哪一个似乎没有一致性,我希望他们只使用ClassLoader参数而不是猜测.
Class.forName(String)的Javadoc 状态(强调我的):
返回与具有给定字符串名称的类或接口关联的Class对象.调用此方法等效于:Class.forName(className,true,currentLoader)其中currentLoader表示当前类的定义类加载器.
换句话说,该方法不会自动使用系统类加载器 - 它使用物理定义调用它的类的加载器.从Java语言规范,第5.3节:
类加载器L可以通过直接定义C或通过委托给另一个类加载器来创建C. 如果L直接创建C,我们说L定义C或者等价地说L是C的定义加载器.
您的自定义类加载器不Main直接创建类 - 它委托给父加载器来创建类,因此它是在您调用Class.forName(String)方法时将使用的父类加载器Main.如果要直接使用自定义类加载器,则需要使用3-arg变量显式指定它,Class.forName或者更改自定义类加载器实现,以便它实际加载有问题的类(通常通过扩展URLClassLoader).
| 归档时间: |
|
| 查看次数: |
3816 次 |
| 最近记录: |