在JVM类加载器中,我们介绍除了最基础的3种类加载器互相配合进行加载的,我们如果有必要,是加入自己定义的类加载器。
这里我们就来自定义一个类加载器,用于实现对类进行加密和解密处理。
首先新建一个User.java
文件,其中内容具体如下:
package com.rockvine.loader;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class User {
private String name = "Rocky";
private int age = 18;
// 省略Getter、Setter方法
// ...
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
选中User.java
文件,在菜单栏的Build
中,点击 ReCompile 'User.java'
,即可编译获取到User.class
文件。
然后对编译生成的User.class
进行加密,至于加密方法使用最简单的方式进行演示,利用Java位运算符中的 ^ 按位异或
运算进行处理,一个数异或同一个数两次,结果还是那个数。
然后直接将的User.class
移动至项目中,然后将其复制一份重命名为UserSrc.class
,再利用异或操作来覆盖原User.class
文件。
public class XorEncryptUtil {
// 异或操作, 可以进行加密和解密
private static void xor(InputStream in, OutputStream out) throws Exception {
int ch;
while (-1 != (ch = in.read())) {
ch = ch ^ 0xff;
out.write(ch);
}
}
// 加密方法
public static void encrypt(File src, File des) throws Exception {
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(des);
xor(in, out);
in.close();
out.close();
}
// 解密方法
public static byte[] decrypt(File src) throws Exception {
InputStream in = new FileInputStream(src);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
xor(in, bos);
byte[] data = bos.toByteArray();
in.close();
bos.close();
return data;
}
}
public class XorEncryptTest {
public static void main(String[] args) throws Exception {
String dirPath = System.getProperty("user.dir");
String basePath = dirPath + "/jvm/src/main/java/com/rockvine/loader";
File src = new File(basePath + "/UserSrc.class");
File des = new File(basePath + "/User.class");
XorEncryptUtil.encrypt(src, des);
}
}
可以使用Sublime打开加密后的User.class
文件进行查看,根据类文件结构及字节码指令介绍的,可以发现class文件类结构已经被破坏了,如常见的魔数0xcafebabe之类的。
现在就需要实现一个自己的类加载器,用于对加密后的class文件在运行时,先解密再加载,首先必须要继承的就是 ClassLoader
这个抽象类。
ClassLoader类中的loadClass()
方法是实现双亲委托模型逻辑的地方,擅自修改这个方法会导致模型被破坏,容易造成问题。因此我们最好是在双亲委托模型框架内进行小范围的改动,不破坏原有的稳定结构。
所以为了保证双亲委派模型不被破坏,建议不要重写loadClass()
方法,而是重写findClass()
方法,只在findClass()
里重写自定义类的加载方法。
public class MyClassLoader extends ClassLoader {
private String basePath;
private final static String FILE_EXT = ".class";
public void setBasePath(String basePath) {
this.basePath = basePath;
}
// 解密
private byte[] loadClassData(String name) {
try {
String tempName = name.replaceAll("\\.", System.getProperty("file.separator"));
return XorEncryptUtil.decrypt(new File(basePath + tempName + FILE_EXT));
} catch (Exception e) {
System.out.println("自定义类加载器加载失败,错误原因:" + e.getMessage());
return null;
}
}
@Override
protected Class<?> findClass(String name) {
byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
}
最后可以把User.java
及UserSrc.class
文件删除,只保留加密后的User.class
文件,用自定义的类加载器进行解密加载,结果如下:
public class Client {
public static void main(String[] args) throws Exception {
String dirPath = System.getProperty("user.dir");
String basePath = dirPath + "/jvm/src/main/java/";
MyClassLoader myClassLoader = new MyClassLoader();
myClassLoader.setBasePath(basePath);
Class<?> clazz = myClassLoader.findClass("com.rockvine.loader.User");
System.out.println(clazz.getClassLoader());
Object o = clazz.newInstance();
System.out.println(o);
}
}