为什么自定义系统类加载器不起作用?

时间:2014-08-25 02:20:23

标签: java classloader

我正在尝试使用标记-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甚至没有调用

2 个答案:

答案 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)。