为什么自定义系统类加载器不起作用? [英] Why is custom system classloader not working?

查看:163
本文介绍了为什么自定义系统类加载器不起作用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我正在尝试使用标志 -Djava.system.class.loader = MyLoader 覆盖系统的类加载器。但是,加载类时仍然没有使用 MyLoader



MyLoader 的代码:

 公共类MyLoader扩展ClassLoader {
public MyLoader(ClassLoader parent){
super(S(parent));
}

private static ClassLoader S(ClassLoader cl){
System.out.println(--- MyLoader --- inside #constructor(+ cl +)) ...);
返回cl;
}

@Override
public Class<?> loadClass(String name,boolean resolve)抛出ClassNotFoundException {
System.out.println(--- MyLoader --- inside loadClass(+ name +,+ resolve +)...);
返回super.loadClass(name,resolve);
}
}

这是主要代码:

  public class Main {
public static void main(final String args [])抛出异常{
System.out.println (---主要---第一线);
System.out.println(--- Main --- getSystemClassLoader():+ ClassLoader.getSystemClassLoader());
System.out.println(--- Main --- getSystemClassLoader()的loader:+ ClassLoader.getSystemClassLoader()。getClass()。getClassLoader());
Call(javax.crypto.Cipher);
}

public static void调用(final String class_name)抛出异常{
System.out.println(--- Main ---调用Class.forName(+ class_name +)...);
Class.forName(class_name);
System.out.println(--- Main --- call complete);
}
}

这是使用命令<$ c $的输出c> java -Djava.system.class.loader = MyLoader -verbose -Xshare:off (参见 Eclipse运行配置):


[已打开C:\Program Files\Java \ jre7 \lib \\\ .jar]



[从C:\Program Files \Java \ jre7 \lib \rt.jar加载java.lang.Object]



[已加载java.io.可序列化来自C:\Program
Files\Java\jre7\lib\rt.jar]



//等等..省略,因为它太长了



[从
文件加载MyLoader:/ C:/ Documents%20和%20Settings / Owner / Desktop /程序/ Eclipse%20Workspace%202 / Test93 / bin /]



--- MyLoader --- 在#constructor里面(sun.misc.Launcher $ AppClassLoader @ 1580 46e) ...



[从C:\程序中加载sun.launcher.LauncherHelper
文件\ Java \ jre7 \ lib \rt.jar]



[从C:\程序中加载java.lang.StringCoding
文件\ Java @ \\ jre7 \ lib \\ \\ t \\ t \\ t \\ t \\ b \\ p>

[从C:\ Program
文件中添加java.lang.StringCoding $ StringDecoder文件\ Java \ jre7 \ lib \\ \\ t \\ t \\ thttp:

--- MyLoader --- 里面的loadClass(Main,false) ...



[从文件中加载主文件:/ C:/ Documents%20和%20Settings / Owner / Desktop / Programs / Eclipse%20Workspace%202 / Test93 / bin /]



[从C:\Program Files \Java \ jre7 \lib \\\ .jar加载java.lang.Void]



--- Main ---第一行



--- 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 b $ b Files\Java\jre7\lib\jce.jar]



---主要--- 致电完成


可以看出,即使使用<$ c加载 Main $ c> MyLoader , javax.crypto.Cipher 未使用 MyLoader 加载。输出显示 MyLoader.loadClass 仅被调用一次。



为什么 MyLoader。 loadClass 当从jce.jar加载 javax.crypto.Cipher 时,甚至调用

解决方案

您的问题是您的自定义类加载器用于加载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 从bootstrap类加载java。* loader但是从其自己的类加载器图中加载其他JDK类(例如sun。*和javax。*)。

  / * *使用-Djava.system.class.loader = MyLoader运行以使用此类加载器。 * / 
公共静态类MyLoader扩展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)抛出ClassNotFoundException {
System.out.println(--- MyLoader --- inside loadClass(+ name +,+ resolve +)...);
返回super.loadClass(name,resolve);
}
}

至于JDK中的SPI工厂(想想XML解析器)和crypto实现),他们使用反射从ContextClassLoader或SystemClassLoader或一个接一个地加载命名类,因为他们希望您能够定义自己的实现,并且引导类加载器不加载用户定义的类。他们使用的两个中的哪一个似乎没有一致性,我希望他们只使用ClassLoader参数而不是猜测。


I'm trying to override the system's class loader using using the flag -Djava.system.class.loader=MyLoader. However, MyLoader is still not being used when classes are loaded.

MyLoader's code:

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

This is the main code:

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");
    }
}

This is the output using the command java -Djava.system.class.loader=MyLoader -verbose -Xshare:off (cf. Eclipse run config):

[Opened C:\Program Files\Java\jre7\lib\rt.jar]

[Loaded java.lang.Object from C:\Program Files\Java\jre7\lib\rt.jar]

[Loaded java.io.Serializable from C:\Program Files\Java\jre7\lib\rt.jar]

// etc etc... omitted since it's too long

[Loaded MyLoader from file:/C:/Documents%20and%20Settings/Owner/Desktop/Programs/Eclipse%20Workspace%202/Test93/bin/]

---MyLoader--- inside #constructor(sun.misc.Launcher$AppClassLoader@158046e)...

[Loaded sun.launcher.LauncherHelper from C:\Program Files\Java\jre7\lib\rt.jar]

[Loaded java.lang.StringCoding from C:\Program Files\Java\jre7\lib\rt.jar]

[Loaded java.lang.StringCoding$StringDecoder from C:\Program Files\Java\jre7\lib\rt.jar]

---MyLoader--- inside loadClass(Main, false)...

[Loaded Main from file:/C:/Documents%20and%20Settings/Owner/Desktop/Programs/Eclipse%20Workspace%202/Test93/bin/]

[Loaded java.lang.Void from C:\Program Files\Java\jre7\lib\rt.jar]

---Main--- first line

---Main--- getSystemClassLoader(): MyLoader@8697ce

---Main--- getSystemClassLoader()'s loader: sun.misc.Launcher$AppClassLoader@158046e

---Main--- calling Class.forName(javax.crypto.Cipher)...

[Opened C:\Program Files\Java\jre7\lib\jce.jar]

[Loaded javax.crypto.Cipher from C:\Program Files\Java\jre7\lib\jce.jar]

---Main--- call complete

As can be seen, even though Main is loaded using MyLoader, javax.crypto.Cipher is not loaded using MyLoader. The output shows that MyLoader.loadClass is only called once.

Why is MyLoader.loadClass not even called when javax.crypto.Cipher is being loaded from jce.jar?

解决方案

Your problem is that your custom class loader is being used to load Main, but its loadClass simply delegates to the parent class loader to load Main. Therefore. within Main, if you called Main.class.getClassLoader(), it would return the sun.misc.Launcher$AppClassLoader, not MyLoader.

To see what class loader will be used for Class.forName(String) calls and symbolic references from your own class, you should print getClass().getClassLoader() (or MyClass.class.getClassLoader() from a static method). The class loader that is used is the one that defined the class whose code is currently being executed. This is the rule everywhere except when using reflection (Class.forName(String, boolean, ClassLoader)).

Once a class is loaded from the parent class loader, any classes that it loads will also use that primitive class loader. So once Main is loaded from the sun.misc.Launcher$AppClassLoader class loader, all the classes that it calls will come from that same class loader, not from your own MyLoader. Similarly, once the javax.crypto.Cypher class is loaded from the null (aka Bootstrap) class loader, any classes that it mentions will also come from the bootstrap class loader except the classes it loads using reflection (SPI).

To stop loading classes from the sun.misc.Launcher$AppClassLoader class loader, set MyLoader's CLASSPATH to AppClassLoader's CLASSPATH and don't delegate classloading to AppClassLoader. Note that this will cause all the CLASSPATH classes to be loaded from MyLoader, but the classes from JDK will in general still be loaded from the null (Bootstrap) class loader.

To stop loading JDK classes from the bootstrap class loader, you must explicitly put the JDK into the classpath and modify loadClass to not check the parent first for some classes. Loading JDK classes from your own class loader is delicate; some classes (e.g. java.lang.String) must be loaded from the boostrap class loader. This is not something I have tried myself, but I have read that OSGi loads java.* from the bootstrap class loader but loads other JDK classes (e.g. sun.* and javax.*) from its own graph of class loaders.

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

As for the SPI factories within JDK (think XML parsers and crypto implementations), they use reflection to load named classes from either the ContextClassLoader or the SystemClassLoader or one after the other, because they want you to be able to define your own implementation, and the bootstrap class loader does not load user-defined classes. There seems to be no consistency in which one of the two they used, and I wish they just took a ClassLoader parameter instead of guessing.

这篇关于为什么自定义系统类加载器不起作用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆