当前位置: 首页 > 面试题库 >

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

赫连睿
2023-03-14
问题内容

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

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程序包中的许多其他类。这些类显然正在加载中,因为execd
python代码能够执行操作并读取我的文件。但是,由于某些原因,那些类并未由load的类加载器加载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本身的内部。

这是您尝试创建的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构造函数调用。如果您对在加载时弄乱类感到好奇,我将检查该项目。



 类似资料:
  • 我有一个Advision类,它使用了代理jar中的另一个类,但这个类依赖于代理jar中不存在的一个类。代理jar中不存在的这个类存在于应用程序类加载器上的另一个jar中。代理jar位于系统类加载器上。所以我没有遇到ClassDefounderRor。我已经尝试使用Transformer.forAdvise,这是在另一篇文章中建议的,但这只适用于Advise类。

  • 本文向大家介绍java 类加载与自定义类加载器详解,包括了java 类加载与自定义类加载器详解的使用技巧和注意事项,需要的朋友参考一下 类加载 所有类加载器,都是ClassLoader的子类。 类加载器永远以.class运行的目录为准。 读取classpath根目录下的文件有以下几种方式: 1 在Java项目中可以通过以下方式获取classspath下的文件: 在Tomcat中tomcat又声明了

  • 我试图修改几个类的字节代码,这些类的打包jar文件不在类路径中-它们是在给定URL的运行时由自定义的加载的。我尝试使用和来拦截那些类,但失败了。类加载器是遗留项目的一部分,因此我无法直接对其进行更改。 代理可以很好地处理AppClassLoader“本地”加载的类,但只会忽略那些由自定义类加载器加载的类。 CustomClassLoader: 我的代理中使用的ClassFileTransforme

  • 我一直在Java玩简单的自定义类加载器,到目前为止,对于非模块相关的类来说,一切都按照预期工作。但是,我似乎找不到任何方法来使用我的类加载器从模块加载类,即使与模块相关的方法已经被重载。我尝试做的是从模块“HelloModularWorld”加载一个类,并运行它的main。然而,当我指定包所在的目录时,它将“正常”加载,并报告为在未命名模块中。我错过了什么? 类加载器只是从文件系统的其他地方加载类

  • 我刚刚开始使用composer进行依赖关系管理,我很难弄清楚我是如何不遵守psr-4进行自动加载的,所以我来这里寻求建议。我有一个生成随机值的类,它已经在packagist上了。项目结构如下(我已将composer.json文件标记为A和B): 项目总监 composer.jsonA composer.jsonB 最后是RlandValue.php文件,它声明了ejFrancis命名空间 当我运行

  • 我不熟悉使用composer和psr-0。我尝试了一个使用composer和psr-0的小应用程序。我已经使用名称空间加载了一个特定的类。当我使用composer vendor/autoload调用一个类时,我得到了“未找到类”错误。 我的composer.json文件:/var/www/html/silexapp/composer.json 我的作曲家供应商自动加载文件:/var/www/htm