Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
184 views
in Technique[技术] by (71.8m points)

java - Why is custom system classloader not working?

I'm trying to override the system's class loader 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 Main (cf. Eclipse run config):

[Opened C:Program FilesJavajre7lib
t.jar]
[Loaded java.lang.Object from C:Program FilesJavajre7lib
t.jar]
[Loaded java.io.Serializable from C:Program
FilesJavajre7lib
t.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 FilesJavajre7lib
t.jar]
[Loaded java.lang.StringCoding from C:Program FilesJavajre7lib
t.jar]
[Loaded java.lang.StringCoding$StringDecoder from C:Program FilesJavajre7lib
t.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 FilesJavajre7lib
t.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 FilesJavajre7libjce.jar]
[Loaded javax.crypto.Cipher from C:Program FilesJavajre7libjce.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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...