17

我一直在尝试设置一个自定义类加载器来拦截类以打印出哪些类正在加载到应用程序中。类加载器看起来像这样

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

它只是吐出它加载的所有类的名称。但是,当我尝试运行一些代码时,

import org.python.util.PythonInterpreter;
public class Scripts {
    public String main(){

        PythonInterpreter p = new PythonInterpreter();
        p.exec("print 'Python ' + open('.gitignore').read()");

        return "Success! Nothing broke";
    }
}

通过

MyClassLoader bcl = new MyClassLoader();
Class c = bcl.loadClass("Scripts");

Method m = c.getMethod("main");
String result = (String) m.invoke(c.getConstructor().newInstance());

它打印出来

Loading: Scripts
Loading: java.lang.Object
Loading: java.lang.String
Loading: org.python.util.PythonInterpreter
Python build/
.idea/*
*.iml
RESULT: Success! Nothing broke

这似乎很奇怪。org.python.util.PythonInterpreter不是一个简单的类,它依赖于包中的一大堆其他类org.python.util。这些类显然正在加载,因为exec'd python 代码能够做一些事情并读取我的文件。但是,由于某种原因,这些类没有被加载的类加载器加载PythonInterpreter

这是为什么?我的印象是用于加载类的类加载器C将用于加载 所需的所有其他类C,但这显然不会发生在这里。这个假设是错误的吗?如果是,我如何设置它以使C我的类加载器加载的所有传递依赖项?

编辑:

建议使用 using 进行一些实验URLClassLoader。我修改了代表团loadClass()

try{
    byte[] output = IOUtils.toByteArray(this.getResourceAsStream(name));
    return instrument(defineClass(name, output, 0, output.length));
}catch(Exception e){
    return instrument(super.loadClass(name));
}

以及使 MyClassLoader 子类 URLClassLoader 而不是普通的 ClassLoader,通过以下方式获取 URL:

super(((URLClassLoader)ClassLoader.getSystemClassLoader()).getURLs());

但这似乎不是正确的事情。特别是,getResourceAsStream()对于我请求的所有类,甚至是像 Jython lib 这样的非系统类,都向我抛出空值。

4

5 回答 5

20

类加载基础

有两个主要的地方可以扩展类加载器来改变类的加载方式:

  • findClass(String name) - 当您想要查找具有通常的父级优先委托的类时,您将覆盖此方法。
  • loadClass(String name, boolean resolve) - 当您想要更改类加载委托的完成方式时,请覆盖此方法。

但是,类只能来自 java.lang.ClassLoader 提供的最终 defineClass(...) 方法。由于您想捕获所有已加载的类,因此我们需要覆盖 loadClass(String, boolean) 并在其中某处调用 defineClass(...)。

注意:在 defineClass(...) 方法内部,有一个 JNI 绑定到 JVM 的本机端。在该代码内部,检查 java.* 包中的类。它只会让系统类加载器加载这些类。这可以防止您弄乱 Java 本身的内部结构。

一个示例子 First ClassLoader

这是您尝试创建的 ClassLoader 的一个非常简单的实现。它假定您需要的所有类都可用于父类加载器,因此它只使用父类作为类字节的源。为了简洁起见,此实现使用 Apache Commons IO,但它很容易被删除。

import java.io.IOException;
import java.io.InputStream;

import static org.apache.commons.io.IOUtils.toByteArray;
import static org.apache.commons.io.IOUtils.closeQuietly;
...
public class MyClassLoader
  extends ClassLoader {
  MyClassLoaderListener listener;

  MyClassLoader(ClassLoader parent, MyClassLoaderListener listener) {
    super(parent);
    this.listener = listener;
  }

  @Override
  protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    // respect the java.* packages.
    if( name.startsWith("java.")) {
      return super.loadClass(name, resolve);
    }
    else {
      // see if we have already loaded the class.
      Class<?> c = findLoadedClass(name);
      if( c != null ) return c;

      // the class is not loaded yet.  Since the parent class loader has all of the
      // definitions that we need, we can use it as our source for classes.
      InputStream in = null;
      try {
        // get the input stream, throwing ClassNotFound if there is no resource.
        in = getParent().getResourceAsStream(name.replaceAll("\\.", "/")+".class");
        if( in == null ) throw new ClassNotFoundException("Could not find "+name);

        // read all of the bytes and define the class.
        byte[] cBytes = toByteArray(in);
        c = defineClass(name, cBytes, 0, cBytes.length);
        if( resolve ) resolveClass(c);
        if( listener != null ) listener.classLoaded(c);
        return c;
      } catch (IOException e) {
        throw new ClassNotFoundException("Could not load "+name, e);
      }
      finally {
        closeQuietly(in);
      }
    }
  }
}

这是一个简单的监听器接口,用于观察类的加载。

public interface MyClassLoaderListener {
  public void classLoaded( Class<?> c );
}

然后,您可以创建 MyClassLoader 的新实例,将当前类加载器作为父类,并在加载类时对其进行监视。

MyClassLoader classLoader = new MyClassLoader(this.getClass().getClassLoader(), new MyClassLoaderListener() {
  public void classLoaded(Class<?> c) {
    System.out.println(c.getName());
  }
});
classLoader.loadClass(...);

这将在最一般的情况下工作,并允许您在加载类时得到通知。但是,如果这些类中的任何一个创建了自己的子类加载器,那么它们可以绕过此处添加的通知代码。

更高级的类加载

要真正捕获正在加载的类,即使子类加载器覆盖 loadClass(String, boolean),您也必须在正在加载的类和它们可能对 ClassLoader.defineClass(...) 进行的任何调用之间插入代码. 为此,您必须开始使用ASM之类的工具进行字节码重写。我在 GitHub 上有一个名为Chlorine的项目,它使用此方法重写 java.net.URL 构造函数调用。如果您对在加载时弄乱类感到好奇,我会检查该项目。

于 2012-11-16T18:51:59.377 回答
2

如果你这样做

    System.out.println( p.getClass().getClassLoader() );

你会看到那个p类加载器不是你的MyClassLoader bcl。它实际上是由bcl的父系统类加载器加载的。

PythonInterpreter加载它的依赖类时,它将使用它的实际类加载器,系统类加载器,而不是你的bcl,所以你的拦截没有到达。

为了解决这个问题,你的类加载器不能委托给它的父类,它必须自己实际加载类。

为此,您可以子类URLClassLoader化(从系统类加载器中窃取 URL)。

于 2012-11-10T00:22:02.963 回答
2

如果您想在加载类时打印它们,那么在 JVM 上打开 verbose:class 选项怎么样?

java -verbose:class your.class.name.here

要回答您的直接问题:

这是为什么?我的印象是用于加载 C 类的类加载器将用于加载 C 所需的所有其他类,但这显然不会发生在这里。这个假设是错误的吗?如果是,我如何设置它,以便 C 的所有传递依赖项都由我的类加载器加载?

搜索ClassLoader 时,搜索是从叶子 ClassLoader 到根执行的,当 Java 确定必须加载一个新类时,它会从 ClassLoader 树的返回到启动类解析的叶子。

为什么?考虑您的自定义类是否想要从 Java 标准库中加载某些内容。正确的答案是这应该由系统类加载器加载,这样才能最大限度地共享类。尤其是当您认为正在加载的类可能会加载更多的类。

这也解决了一个问题,即您最终可能会在不同的 ClassLoader 中加载多个系统 Classes 实例 - 每个 ClassLoader 都具有相同的完全限定名称。EDIT类将在其 ClassLoader 中正确解析。然而有两个问题。

  1. 假设我们有两个 String 实例,a并且b. a.getClass().isInstance(b)如果并且在不同的 ClassLoader 中实例化,则a.getClass() == b.getClass()不是真的。这将导致可怕的问题。ab
  2. 单例:它们不会是单例 - 每个 ClassLoader 可以有一个。

结束编辑

另一项观察:就像您设置了一个 ClassLoader 来专门从中加载类一样,解释器通常自己创建 ClassLoader 实例,它们将解释环境和脚本加载到其中。这样,如果脚本发生更改,可以删除 ClassLoader(以及脚本),并在新的 ClassLoader 中重新加载。EJB 和 Servlet 也使用这个技巧。

于 2012-11-15T07:31:39.900 回答
0

如果您覆盖其他 loadClass() 方法怎么办?

protected Class<?> loadClass(String name, boolean resolve)
于 2012-11-09T20:30:02.700 回答
0

在实例化 PythonInterpreter 之前,您可以使用该PySystemState对象指定自定义类加载器。

PySystemState state = new PySystemState();
state.setClassLoader(classLoader);
PythonInterpreter interp = new PythonInterpreter(table, state);

http://wiki.python.org/jython/LearningJython

于 2012-11-09T20:41:16.980 回答