Java实现的自定义类加载器
本文讲述了如何实现一个Java的自定义类加载器
ClassLoader类有如下两个关键方法:
- loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象
- findClass(String name):根据二进制名称来查找类
如果需要实现自定义的ClassLoader,可以通过重写以上两个方法来实现,当然我们推荐重写findClass()方法,而不是重写loadClass()方法
补充: 自定义类加载器常用功能
- 执行代码前自动验证数字签名
- 根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译class文件
- 根据用户需求来动态地加载类
- 根据应用需求把其他数据以字节码的形式加载到应用中
代码如下:
MyClassLoader类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method;
public class MyClassLoader extends ClassLoader {
public static void main(String[] args) throws Exception { if (args.length < 1) { System.out.println("缺少目标类,请按如下格式运行Java源文件: "); System.out.println("java MyClassLoader ClassName"); } String aimClass = args[0];
String[] params = new String[args.length - 1]; System.arraycopy(args, 1, params, 0, params.length);
MyClassLoader myClassLoader = new MyClassLoader(); Class<?> clazz = myClassLoader.loadClass(aimClass);
Method main = clazz.getMethod("main", String[].class); Object[] argsArray = {params}; main.invoke(null, argsArray); }
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> clazz = null;
String fileStub = name.replace(".", "/"); String javaFilename = fileStub + ".java"; String classFilename = fileStub + ".class"; File javaFile = new File(javaFilename); File classFile = new File(classFilename);
if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { if (!compile(javaFilename) || !classFile.exists()) { throw new ClassNotFoundException("ClassNotFoundException:" + javaFilename); } } catch (IOException ex) { ex.printStackTrace(); } } if (classFile.exists()) { try { byte[] raw = getBytes(classFilename); clazz = defineClass(name, raw, 0, raw.length); } catch (IOException ie) { ie.printStackTrace(); } } if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; }
private boolean compile(String javaFile) throws IOException { System.out.println("MyClassLoader: 正在编译 " + javaFile + "..."); Process p = Runtime.getRuntime().exec("javac " + javaFile);
try { p.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); }
int ret = p.exitValue();
return ret == 0; }
private byte[] getBytes(String filename) throws IOException { File file = new File(filename); long len = file.length(); byte[] raw = new byte[(int) len]; try (FileInputStream fis = new FileInputStream(file)) { int r = fis.read(raw); if (r != len) throw new IOException("无法读取全部文件:" + r + " != " + len); return raw; } }
}
|
说明:
- getBytes(String filename)方法: 从class文件中读取二进制内容
- compile(String javaFile)方法: 调用系统的javac命令编译文件
- 重写父类ClassLoader的findClass(String name)方法: 通过getBytes方法读入文件内容, 然后通过父类的defineClass方法将二进制数据转为Class对象并返回;
- main方法: 读入命令行启动参数, 调用findClass方法编译并加载对应的类, 最后使用反射构造类对象对应的实例, 并通过反射调用实例的main方法
自定义加载器的测试加载类Hello:
1 2 3 4 5 6 7 8 9
| public class Hello {
public static void main(String[] args) { for (String arg : args) { System.out.println("运行Hello的参数:" + arg); } }
}
|
运行
1 2 3 4
| $ java MyClassLoader.java Hello UseMyClassLoader
MyClassLoader: 正在编译 Hello.java... 运行Hello的参数:UseMyClassLoader
|
说明:
- 使用
java MyClassLoader.java直接编译运行MyClassLoader类, 并且指定编译并加载Hello类;
- MyClassLoader在main方法中调用系统命令Javac 编译Hello.java文件, 之后通过loadClass()方法加载Hello.class字节码;
- 通过反射调用Hello类的main方法, 输出:
运行Hello的参数:UseMyClassLoader