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 ビット演算子の演算を使用し^ 按位异或
て、同じ数値を 2 回 XOR して処理しても、結果は同じ数値になることを示す最も簡単な方法を使用します。
User.class
次に、プロジェクトに直接移動し、コピーして名前を変更しUserSrc.class
、XOR 演算を使用して元の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
によると、共通のマジック ナンバー 0xcafebabe など、クラス ファイルのクラス構造が破壊されていることがわかります。
次に、実行時に暗号化されたクラス ファイルを復号してロードするために使用される独自のクラス ローダーを実装する必要があります。最初に継承する必要があるのは、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);
}
}