繁体   English   中英

自定义Java类加载器没有用于加载依赖项?

[英]Custom Java classloader not being used to load dependencies?

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

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的python代码能够执行操作并读取我的文件。 但是出于某种原因,加载PythonInterpreter的类加载器不会加载这些类。

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

编辑:

一些使用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这样的非系统类。

类加载的基础知识

扩展类加载器有两个主要的位置来改变类的加载方式:

  • findClass(String name) - 如果要查找具有通常父级第一委派的类,则覆盖此方法。
  • loadClass(String name,boolean resolve) - 当您想要更改类加载委派的方式时,重写此方法。

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

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

示例Child 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构造函数调用。 如果你对在加载时搞乱课程感到好奇,我会检查那个项目。

如果你这样做

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

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

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

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

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

如果要在加载类时打印类,请如何在JVM上打开verbose:class选项?

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

要回答您的直接问题:

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

搜索 ClassLoaders时,从叶子ClassLoader到根执行搜索,当Java计算出必须加载新类时,它从ClassLoader树的执行,然后返回到启动类解析的叶子。

为什么? 考虑一下您的自定义类是否想要从Java标准库中加载一些东西。 正确的答案是,这应该由System ClassLoader加载,以便最大程度地共享类。 特别是当您考虑到正在加载的类可能会加载更多类时。

这也解决了可能最终将多个系统类实例加载到不同的ClassLoader中的问题 - 每个类实例都具有相同的完全限定名称。 编辑类将在其ClassLoader中正确解析。 但是有两个问题。

  1. 假设我们有两个String实例, ab 如果ab在不同的ClassLoader中实例化,则a.getClass().isInstance(b)a.getClass() == b.getClass()不为true。 这会引起可怕的问题。
  2. 单身人士:他们不会是单身人士 - 每个ClassLoader你可以拥有一个。

结束编辑

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

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

protected Class<?> loadClass(String name, boolean resolve)

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM