Java类加载器如何为“常规”环境工作(非明确使用类加载器)

时间:2009-08-19 16:58:47

标签: java classloader

我正在研究类路径的动态修改。我找到了one solution that works nicely,但它使用了对addURL()的显式调用。 (大概是在创业时)

但是,如果默认的类加载器似乎无法找到它,我想在运行时拦截类加载过程以找到类。我尝试将ClassLoader子类化,因此它只是将findClass()loadClass()委托给默认值,并打印出一个调试行,告诉我这些方法已被调用,但它们似乎永远不会被调用我的类通过隐式类加载使用依赖类,例如

// 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()的简单情况相比)

以下是我在自定义类加载器上的尝试。如果我使用带有{"some.package.SomeClass", "foo", "bar", "baz"}的参数列表的DynClassLoader0.main(),并且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()永远不会被调用。

2 个答案:

答案 0 :(得分:4)

引用ClassLoader JavaDoc

  

。的方法和构造   由类加载器创建的对象可以   参考其他课程。确定   提到的类,Java   虚拟机调用loadClass   类加载器的方法   最初创建了这个类。

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

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


编辑:我开始追踪你的例子,但我决定自己更容易。如果您要编写自己的类加载器,我建议从现有的URLClassLoader开始,因为它处理了很多幕后的东西。

因此,MyClassLoader只接受一个JARfile /目录并为该目录加载类。我已经重写了调用加载类的三个方法,只需记录它们的调用(使用System.err,因为它不会缓冲输出,与System.out不同)。

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

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

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

抛出异常,当我打印出它的类加载器时,我发现它与我创建的MyLoader实例相同。我还打印出Exception.class的加载器,它是null - Class.getClassLoader()的JavaDoc表示启动类加载器。

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。

在尝试完成请求本身(这是在ClassLoader JavaDoc中)之前,期望类加载器将请求委托给其父。这种做法有几个好处,最重要的是你不会无意中加载同一类的不兼容实例。

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

答案 1 :(得分:1)

在您的代码中,对super.loadClass()的调用将类的加载委托给父类加载器(只需查看java.lang.ClassLoader#loadClass的实现)。因此,DynClassLoader0的实例不是加载类,而是currentThreadClassLoader(您从Thread.currentThread().getContextClassLoader()获取的)作为构造函数参数传递给DynClassLoader0的实例。当加载的类引用其他类时,它们也会被该类加载器加载而不是DynClassLoader0