类加载基础知识
有两个主要的地方可以扩展类加载器来改变类的加载方式:
- findClass(String name) - 当您想要时可以覆盖此方法
找一个平时家长先委派的班级。
- loadClass(String name, boolean resolve) - 当您想要更改时覆盖此方法
类加载委托的完成方式。
然而,类只能来自java.lang.ClassLoader 提供的最终defineClass(...) 方法。由于您希望捕获所有已加载的类,因此我们需要重写 loadClass( String, boolean ) 并在其中的某个位置调用 DefineClass(...) 。
NOTE:在defineClass(...) 方法内部,有一个到JVM 本机端的JNI 绑定。在该代码内部,会检查 java.* 包中的类。它只会让系统类加载器加载这些类。这可以防止您弄乱 Java 本身的内部结构。
子级第一类加载器示例
这是您尝试创建的类加载器的一个非常简单的实现。它假设您需要的所有类都可供父类加载器使用,因此它仅使用父类作为类字节的源。为了简洁起见,此实现使用 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 http://asm.ow2.org。我有一个项目叫Chlorine https://github.com/ctrimble/chlorineGitHub 上使用此方法重写 java.net.URL 构造函数调用。如果您对在加载时弄乱类感到好奇,我会检查该项目。