我一直在尝试建立一个自定义的类加载器,该类加载器将拦截类以打印出哪些类正在加载到应用程序中。类加载器看起来像这样
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。
org.python.util.PythonInterpreter
org.python.util
exec
PythonInterpreter
这是为什么?我给人的印象是,用于加载类的类加载器C将用于加载所需的所有其他类C,但这显然不在这里发生。这个假设有误吗?如果是的话,我该如何设置它C以使我的类加载器加载的所有传递依赖项?
C
编辑:
URLClassLoader建议使用进行一些实验。我在loadClass()以下位置修改了代表团:
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这样的非系统类,都向我扔空值。
getResourceAsStream()
有两个主要地方来扩展类加载器,以更改类的加载方式:
但是,类只能来自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构造函数调用。如果您对在加载时弄乱类感到好奇,我将检查该项目。