有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

我一直在尝试设置一个自定义类加载器,该加载器拦截类,以打印出哪些类正在加载到应用程序中。类加载器如下所示

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的实验,这是建议的。我在^{中修改了委托:

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这样的非系统类,都抛出空值


共 (5) 个答案

  1. # 1 楼答案

    如果重写另一个loadClass()方法会怎么样

    protected Class<?> loadClass(String name, boolean resolve)
    
  2. # 2 楼答案

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

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

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

  3. # 3 楼答案

    如果你这样做

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

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

    PythonInterpreter加载它的依赖类时,它将使用它的实际类加载器,即系统类加载器,而不是您的bcl,因此不会达到您的拦截

    要解决这个问题,类加载器不能委托给其父类,它必须自己实际加载类

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

  4. # 4 楼答案

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

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

    要回答您的直接问题:

    Why is that? I was under the impression that the classloader used to load a class C would be used to load all the other classes needed by C, but that's clearly not happening here. Is that assumption mistaken? If it is, how do i set it up such that all the transitive dependencies of C are loaded by my classloader?

    搜索类加载器时,搜索从叶类加载器执行到根,当Java计算出必须加载新类时,搜索从类加载器树的执行到启动类解析的叶

    为什么??考虑一下您的自定义类是否想从java标准库中加载一些东西。正确的答案是,这应该由系统类加载器加载,以便最大限度地共享类。特别是当你认为正在加载的类会潜在地加载更多的类时。

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

    1. 假设我们有两个字符串实例,ab^如果在不同的类加载器中实例化了ab,则{}和a.getClass() == b.getClass()不是真的。这将导致可怕的问题
    2. 单例:它们不是单例-每个类加载器可以有一个

    结束编辑

    另一个观察结果是:正如您设置了类加载器专门从中加载类一样,解释器通常自己创建类加载器实例,将解释环境和脚本加载到其中。这样,如果脚本发生更改,可以删除类加载器(以及脚本),然后重新加载到新的类加载器中。EJB和servlet也使用这个技巧

  5. # 5 楼答案

    类加载的基础知识

    有两个主要位置可以扩展类加载器以更改类的加载方式:

    • findClass(字符串名称)-您可以在需要时重写此方法 查找具有通常的父级优先委派的类
    • loadClass(字符串名称,布尔解析)-如果要更改,请重写此方法 类加载委托的方式

    但是,类只能来自最终定义类(…)java提供的方法。lang.ClassLoader。由于您希望捕获所有已加载的类,因此我们需要重写loadClass(String,boolean)并使用对defineClass(…)的调用在它的某个地方

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

    一个示例子类加载器

    这是您试图创建的类加载器的一个非常简单的实现。它假定父类装入器可以使用您需要的所有类,因此它只将父类用作类字节的源。为了简洁起见,此实现使用ApacheCommonsIO,但可以很容易地将其删除

    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进行的任何调用之间插入代码。定义类(…)。要做到这一点,您必须开始使用像ASM这样的工具进行字节码重写。我在GitHub上有一个名为Chlorine的项目,它使用此方法重写java。网URL构造函数调用。如果您对在加载时搞乱类感到好奇,我会查看该项目