自定义类加载器加载字节码
自定义类加载器加载字节码
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方法添加的数据移除。