自定义类加载器加载字节码

自定义类加载器加载字节码

Java还提供了一种支持通过网络下载方式加载class的类加载器URLClassLoader,也支持传本地路径,但不能直接传入字节数组加载。而其它类加载器只能通过类名去加载。

由于动态编写字节码生成class或者改写class是在内存中完成的,如果要使用Java提供的几种类加载器去加载,我们需要先将字节码输出到文件并将文件存放在classpath的目录下,因此我们可以自己实现一个类加载器支持直接传入字节数组加载,省略掉这一步骤。

ClassLoader类有三个重要的方法,分别是loadClass、findClass和defineClass。loadClass方法是加载目标类的入口,首先查找当前ClassLoader以及父加载器是否已经加载了目标类,如果都没有加载,且父加载器加载不到这个类,就会调用findClass让自己来加载目标类。ClassLoader的findClass方法是需要子类重写的,在该方法中实现获取目标类的字节码,拿到这个字节码之后再调用defineClass方法将字节码转换成Class对象。

自定义类加载器需要继承ClassLoader,并重写findClass方法,在findClass方法中根据类名取得该类的字节码,再调用父类的defineClass方法完成类的加载。需要注意的是,findClass方法传递进行的类名是以符号“.”拼接的类名,不是“/”。使用“/”符号替代“.”符号的类名称为类的内部名称或内部类名。

自定义类加载器ByteCodeClassLoader的实现如代码清单4-13所示。

代码清单4-13 ByteCodeClassLoader类加载器

public class ByteCodeClassLoader extends ClassLoader {

    // 类名-> 字节码持有者
    private final Map<String, ByteCodeHolder> classes = new HashMap<>();

    public ByteCodeClassLoader(final ClassLoader parentClassLoader) {
        super(parentClassLoader);
    }

    @Override
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
        ByteCodeHolder holder= classes.get(name);
        if (holder != null) {
            byte[] bytes = holder.getByteCode();
            classes.remove(name);
            return defineClass(name, bytes, 0, bytes.length);
        }
        return super.findClass(name);
    }

    public void add(final String name, final ByteCodeHolder holder) {
        classes.put(name, holder);
    }
}

ByteCodeClassLoader使用HashMap缓存类名与其字节码的映射,外部需要先通过调用add方法将需要加载的类添加到Map中,才能调用loadClass方法加载类。在findClass方法中,完成类的加载后将调用add方法添加的数据移除。