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

自定义Java类加载器未用于加载依赖项?,java,class,jvm,metaprogramming,classloader,Java,Class,Jvm,Metaprogramming,Classloader,我一直在尝试设置一个自定义类加载器,该加载器拦截类,以打印出哪些类正在加载到应用程序中。类加载器如下所示 public class MyClassLoader extends ClassLoader { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { System.out.println("Loading: " + name);

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

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

它打印出来了

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
的所有可传递依赖项都由我的类加载器加载

编辑:

建议使用
URLClassLoader
进行一些实验。我在
loadClass()
中修改了委托:

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

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

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

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

受保护类loadClass(字符串名称,布尔解析)

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

PySystemState state = new PySystemState();
state.setClassLoader(classLoader);
PythonInterpreter interp = new PythonInterpreter(table, state);
如果你这样做

    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的所有可传递依赖项都由我的类加载器加载

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

为什么??考虑一下您的自定义类是否想从java标准库中加载一些东西。正确的答案是,这应该由系统类加载器加载,以便最大限度地共享类。特别是当你认为类被加载时,可能会加载更多的类。 这还解决了这样一个问题,即可能会在不同的类加载器中加载多个系统类实例,每个实例都具有相同的完全限定名EDIT类将在其类加载器中正确解析。然而,有两个问题

  • 假设我们有两个字符串实例,
    a
    b
    <如果在不同的类加载器中实例化了
    a
    b
    ,则code>a.getClass().isInstance(b)和
    a.getClass()==b.getClass()
    不为真。这将导致可怕的问题
  • 单例:它们不是单例-每个类加载器可以有一个 结束编辑

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

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

    • findClass(字符串名称)-您可以在需要时重写此方法 查找具有通常的父级优先委派的类
    • loadClass(字符串名称,布尔解析)-如果要更改,请重写此方法 类加载委托的方式
    但是,类只能来自java.lang.ClassLoader提供的最终defineClass(…)方法。由于您希望捕获所有已加载的类,因此我们需要重写loadClass(String,boolean)并在其中的某个位置使用对defineClass(…)的调用

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

    一个示例子类加载器 这是您试图创建的类加载器的一个非常简单的实现。它假定父类装入器可以使用您需要的所有类,因此它只将父类用作类字节的源。这个实现使用ApacheCommonsIO来简化,但是很容易实现
    protected Class<?> loadClass(String name, boolean resolve)
    
    PySystemState state = new PySystemState();
    state.setClassLoader(classLoader);
    PythonInterpreter interp = new PythonInterpreter(table, state);
    
        System.out.println( p.getClass().getClassLoader() );
    
    java -verbose:class your.class.name.here
    
    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 classLoader = new MyClassLoader(this.getClass().getClassLoader(), new MyClassLoaderListener() {
      public void classLoaded(Class<?> c) {
        System.out.println(c.getName());
      }
    });
    classLoader.loadClass(...);