java - Java 类加载器如何在 "regular"情况下工作(类加载器的非显式使用)

标签 java classloader

我正在研究类路径的动态修改。我找到了 one solution that works nicely但它是通过显式调用 addURL() 来实现的。 (大概在启动时)

但是,如果默认类加载器似乎找不到类,我想在运行时拦截类加载过程以定位类。我试图子类化 ClassLoader所以它只是将 findClass()loadClass() 委托(delegate)给默认值,并打印出一条调试行,告诉我这些方法已被调用,但它们似乎从未被调用过当我的类通过隐式类加载使用依赖类时,例如

// regular object instantiation with 'new'
BrowserLauncher launcher;
launcher = new BrowserLauncher();

// static methods
Foobar.doSomethingOrOther();

// Class.forName()
Class cl = Class.forName("foo.bar.baz");

// reflection on a Class object obtained statically
Class<Foobar> cl = Foobar.class;
// do something with cl, like call static methods or newInstance()

类加载在这些情况下是如何工作的?(相对于显式调用 Classloader.loadClass() 的更简单的情况)

下面是我对自定义类加载器的尝试。如果我将 DynClassLoader0.main() 与参数列表 {"some.package.SomeClass", "foo", "bar", "baz"} 一起使用,并且 some.package.SomeClass 引用其他使用上面列出的方法之一在外部 .jar 文件中找到的类,为什么我的 DynClassLoader0 的 findClass() 和 loadClass() 没有被调用?唯一一次调用 loadClass 是在下面的 main() 函数中显式调用 loadClass。

package com.example.test.classloader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class DynClassLoader0 extends ClassLoader {
    public DynClassLoader0()
    {
        super();
    }
    public DynClassLoader0(ClassLoader parent)
    {
        super(parent);
    }
    public void runMain(String classname, String[] args) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
    {
    // [***] here we explicitly use our classloader.
        Class<?> cl = loadClass(classname);
        Method main = cl.getMethod("main", String[].class);
        main.invoke(null, new Object[] {args});
    }

    @Override protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        System.out.println("findClass("+name+")");
        return super.findClass(name);
    }

    @Override public Class<?> loadClass(String name) throws ClassNotFoundException
    {
        System.out.println("loadClass("+name+")");
        return super.loadClass(name);
    }

    static public void main(String[] args)
    {
        // classname, then args
        if (args.length >= 1)
        {
            String[] classArgs = new String[args.length-1];
            System.arraycopy(args, 1, classArgs, 0, args.length-1);

            ClassLoader currentThreadClassLoader
             = Thread.currentThread().getContextClassLoader();
            DynClassLoader0 classLoader = new DynClassLoader0(currentThreadClassLoader);
            // Replace the thread classloader - assumes
            // you have permissions to do so
            Thread.currentThread().setContextClassLoader(classLoader);

            try {
                classLoader.runMain(args[0], classArgs);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        else
        {
            System.out.println("usage: DynClassLoader {classname} [arg0] [arg1] ...");
        }
    }
}

编辑:我已经看过这些问题了:

编辑: 我认为 kdgregory 在下面所说的是正确的,一旦我明确地使用了我的类加载器(请参阅代码中带有 [***] 的行作为注释),从该类执行的所有代码都将导致从同一类加载器进行隐式类加载。然而,我的 DynClassLoader0.loadClass() 从未被调用,除非在最外层的显式调用期间。

最佳答案

引用自ClassLoader JavaDoc :

The methods and constructors of objects created by a class loader may reference other classes. To determine the class(es) referred to, the Java virtual machine invokes the loadClass method of the class loader that originally created the class.

换句话说,一旦您加载了一个类,该类就会尝试通过加载它的类加载器加载其他类。在普通的 Java 应用程序中,这是系统类加载器,它表示传递给 JVM 的类路径,或者是引导类加载器,用于加载 JVM 运行时。

根据您的需要,有一个变体 Class.forName()以类加载器作为参数。如果您使用它来加载特定类,则该类中的引用应使用指定的类加载器。


编辑:我开始跟踪你的例子,但决定给出我自己的例子会更容易。如果你打算编写自己的类加载器,我建议从现有的 URLClassLoader 开始。 ,因为它处理很多幕后工作。

因此,MyClassLoader 获取单个 JAR 文件/目录并单独加载该目录的类。我已经覆盖了为加载类而调用的三个方法,并简单地记录了它们的调用(使用 System.err,因为它不缓冲输出,这与 System.out 不同)。

我的示例使用了我目前正在使用的库;这很方便,但您可以选择任何您想要的库,只要它不在您的类路径中

main() 方法通过 MyLoader 加载一个类。然后我调用该类的一个方法,我知道会以一种我知道会引发异常的方式调用该方法,该异常也是库的一部分。请注意,我通过反射调用该方法:因为该库不在我的 Eclipse 类路径中,所以我无法使用显式引用对其进行编译。

当我运行这个程序时(在 Linux 的 Sun JDK 1.5 下),我看到了很多对 loadClass() 的调用,既针对我的库中的类,也针对类路径中的类。这是意料之中的:ParseUtil 类引用了许多其他类,并将使用 MyLoader(即它的类加载器)来加载它们。对于那些 MyLoader 在本地找不到的类,它会向上委托(delegate)加载器树。

异常被抛出,当我打印出它的类加载器时,我看到它与我创建的 MyLoader 实例相同。我还打印出 Exception.class 的加载器,它是空的——Class.getClassLoader() 的 JavaDoc says 表示引导类加载器。

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;


public class ClassLoaderExample
{
    private static class MyClassLoader
    extends URLClassLoader
    {
        public MyClassLoader(String path)
        throws Exception
        {
            super(new URL[] { new File(path).toURL() });
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException
        {
            System.err.println("findClass(" + name + ")");
            return super.findClass(name);
        }

        @Override
        protected synchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            System.err.println("loadClass(" + name + "," + resolve + ")");
            return super.loadClass(name, resolve);
        }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException
        {
            System.err.println("loadClass(" + name + ")");
            return super.loadClass(name);
        }
    }


    public static void main(String[] argv)
    throws Exception
    {
        ClassLoader myLoader = new MyClassLoader("/home/kgregory/Workspace/PracticalXml-1.1/target/classes/");
        System.out.println("myLoader = " + myLoader);

        Class<?> parseUtilKlass = myLoader.loadClass("net.sf.practicalxml.ParseUtil");
        Method parseMethod = parseUtilKlass.getDeclaredMethod("parse", String.class);

        try
        {
            parseMethod.invoke(null, "not at all valid XML");
        }
        catch (InvocationTargetException e)
        {
            Throwable ee = e.getCause();
            System.out.println("exception:" + ee);
            System.out.println("exception loader = " + ee.getClass().getClassLoader());

            System.out.println("Exception.class loader = " + Exception.class.getClassLoader());
        }
    }
}

根据今天的评论编辑#2。

类加载器应该在尝试自己完成请求之前将请求委托(delegate)给它的父级(这在 ClassLoader JavaDoc 中)。这种做法有几个好处,最重要的是您不会无意中加载同一类的不兼容实例。

J2EE 类加载器修改了此模型:用于加载 WAR 的类加载器将尝试在包含 EAR 的加载器之前解析类,而 EAR 又会尝试在容器的类加载器之前解析类。这里的目标是隔离:如果 WAR 和它的 EAR 都包含相同的库,那可能是因为两者需要不同的版本(或者它们的构建过程草率)。即使在 J2EE 案例中,我也相信容器类加载器以标准方式进行委托(delegate)。

关于java - Java 类加载器如何在 "regular"情况下工作(类加载器的非显式使用),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1301261/

相关文章:

java - 如何选择类的类加载器?

java - 如何在第三方类中为 java.util.Random 提供特定种子?

java - Windows 8 x64 : Usage: java [-options] class [args. 上的 Maven 3.0.4 错误 ..]

Java - 如何降低 float 精度?

java - 反射和多个 jar

java - 线程的上下文类加载器和普通类加载器的区别

java - Class.forName() 和 ClassNotFoundException

java将字符串转换为xml并解析节点

java - EhCache 3 : How to unwrap statistics bean?

java - 动态创建资源ID android