Li *_*oyi 17 java jvm metaprogramming class classloader
我一直在尝试设置一个自定义类加载器,它拦截类以打印出正在加载到应用程序中的类.类加载器看起来像这样
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("Loading: " + name);
return super.loadClass(name);
}
}
Run Code Online (Sandbox Code Playgroud)
它只是吐出它加载的所有类的名称.但是,当我尝试运行一些代码时,
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";
}
}
Run Code Online (Sandbox Code Playgroud)
通过
MyClassLoader bcl = new MyClassLoader();
Class c = bcl.loadClass("Scripts");
Method m = c.getMethod("main");
String result = (String) m.invoke(c.getConstructor().newInstance());
Run Code Online (Sandbox Code Playgroud)
它打印出来
Loading: Scripts
Loading: java.lang.Object
Loading: java.lang.String
Loading: org.python.util.PythonInterpreter
Python build/
.idea/*
*.iml
RESULT: Success! Nothing broke
Run Code Online (Sandbox Code Playgroud)
这似乎很奇怪.org.python.util.PythonInterpreter不是一个简单的类,它取决于org.python.util包中的一大堆其他类.这些类显然正在被加载,因为exec'd 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));
}
Run Code Online (Sandbox Code Playgroud)
以及使MyClassLoader子类URLClassLoader而不是普通的ClassLoader,通过以下方式获取URL:
super(((URLClassLoader)ClassLoader.getSystemClassLoader()).getURLs());
Run Code Online (Sandbox Code Playgroud)
但这似乎不是正确的事情.特别是,getResourceAsStream()对于我正在请求的所有类,即使是像Jython lib这样的非系统类,也会向我抛出空值.
Chr*_*ble 20
扩展类加载器有两个主要的位置来改变类的加载方式:
但是,类只能来自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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个用于观看类加载的简单监听器接口.
public interface MyClassLoaderListener {
public void classLoaded( Class<?> c );
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以创建MyClassLoader的新实例,将当前类加载器作为父级,并在加载时监视类.
MyClassLoader classLoader = new MyClassLoader(this.getClass().getClassLoader(), new MyClassLoaderListener() {
public void classLoaded(Class<?> c) {
System.out.println(c.getName());
}
});
classLoader.loadClass(...);
Run Code Online (Sandbox Code Playgroud)
这将在最常见的情况下工作,并允许您在加载类时收到通知.但是,如果这些类中的任何一个创建了自己的子类第一类加载器,那么它们可以绕过此处添加的通知代码.
要真正捕获正在加载的类,即使子类加载器重写了loadClass(String,boolean),也必须在要加载的类和它们可能对ClassLoader.defineClass(...)进行的任何调用之间插入代码. .要做到这一点,你必须开始使用像ASM这样的工具进行字节码重写.我在GitHub上有一个名为Chlorine的项目,它使用此方法重写java.net.URL构造函数调用.如果你对在加载时搞乱课程感到好奇,我会检查那个项目.
| 归档时间: |
|
| 查看次数: |
7369 次 |
| 最近记录: |