类自定义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这样的非系统类,都抛出空值
# 1 楼答案
如果重写另一个loadClass()方法会怎么样
# 2 楼答案
在实例化PythonInterpreter之前,可以使用
PySystemState
对象指定自定义类装入器http://wiki.python.org/jython/LearningJython
# 3 楼答案
如果你这样做
您将看到
p
的类加载器不是您的MyClassLoader bcl
。它实际上是由bcl
的父系统类加载器加载的当
PythonInterpreter
加载它的依赖类时,它将使用它的实际类加载器,即系统类加载器,而不是您的bcl
,因此不会达到您的拦截要解决这个问题,类加载器不能委托给其父类,它必须自己实际加载类
为此,您可以将
URLClassLoader
子类化(从系统类加载器中窃取URL)# 4 楼答案
如果您想在加载类时打印它们,那么在JVM上打开verbose:class选项怎么样
要回答您的直接问题:
当搜索类加载器时,搜索从叶类加载器执行到根,当Java计算出必须加载新类时,搜索从类加载器树的根执行到启动类解析的叶
为什么??考虑一下您的自定义类是否想从java标准库中加载一些东西。正确的答案是,这应该由系统类加载器加载,以便最大限度地共享类。特别是当你认为正在加载的类会潜在地加载更多的类时。
这还解决了这样一个问题,即可能会在不同的类加载器中加载多个系统类实例,每个实例都具有相同的完全限定名EDIT类将在其类加载器中正确解析。然而,有两个问题
a
和b
^如果在不同的类加载器中实例化了a
和b
,则{a.getClass() == b.getClass()
不是真的。这将导致可怕的问题李>结束编辑
另一个观察结果是:正如您设置了类加载器专门从中加载类一样,解释器通常自己创建类加载器实例,将解释环境和脚本加载到其中。这样,如果脚本发生更改,可以删除类加载器(以及脚本),然后重新加载到新的类加载器中。EJB和servlet也使用这个技巧
# 5 楼答案
类加载的基础知识
有两个主要位置可以扩展类加载器以更改类的加载方式:
但是,类只能来自最终定义类(…)java提供的方法。lang.ClassLoader。由于您希望捕获所有已加载的类,因此我们需要重写loadClass(String,boolean)并使用对defineClass(…)的调用在它的某个地方
注意:定义类(…)的内部方法,则存在到JVM本机端的JNI绑定。在该代码中,有一个检查java中的类。*包装。它只允许系统类加载器加载这些类。这可以防止您弄乱Java本身的内部结构
一个示例子类加载器
这是您试图创建的类加载器的一个非常简单的实现。它假定父类装入器可以使用您需要的所有类,因此它只将父类用作类字节的源。为了简洁起见,此实现使用ApacheCommonsIO,但可以很容易地将其删除
这是一个简单的监听器接口,用于监视类的加载
然后,您可以创建MyClassLoader的新实例,将当前类装入器作为父类,并在装入类时监视它们
这将在最常见的情况下工作,并允许您在加载类时收到通知。但是,如果这些类中的任何一个创建自己的子类装入器,那么它们可以绕过此处添加的通知代码
更高级的类加载
要真正捕获正在加载的类,即使子类加载器重写了loadClass(String,boolean),也必须在正在加载的类和它们可能对ClassLoader进行的任何调用之间插入代码。定义类(…)。要做到这一点,您必须开始使用像ASM这样的工具进行字节码重写。我在GitHub上有一个名为Chlorine的项目,它使用此方法重写java。网URL构造函数调用。如果您对在加载时搞乱类感到好奇,我会查看该项目