使用代理修改类

首先,确保正在使用的代理在 Manifest.mf 中具有以下属性:

Can-Redefine-Classes: true
Can-Retransform-Classes: true

启动 Java 代理将允许代理访问类 Instrumentation。使用 Instrumentation,你可以调用 addTransformer(ClassFileTransformer 转换器) 。ClassFileTransformers 将允许你重写类的字节。该类只有一个方法,它提供加​​载类的 ClassLoader,类的名称,它的 java.lang.Class 实例,它的 ProtectionDomain,最后是类本身的字节。

它看起来像这样:

byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 
          ProtectionDomain protectionDomain, byte[] classfileBuffer)

纯粹从字节修改类可能需要很长时间。为了解决这个问题,有一些库可用于将类字节转换为更有用的字节。

在这个例子中,我将使用 ASM,但其他​​替代方案,如 Javassist 和 BCEL 具有类似的功能。

ClassNode getNode(byte[] bytes) {
    // Create a ClassReader that will parse the byte array into a ClassNode
    ClassReader cr = new ClassReader(bytes);
    ClassNode cn = new ClassNode();
    try {
        // This populates the ClassNode
        cr.accept(cn, ClassReader.EXPAND_FRAMES);
        cr = null;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return cn;
}

从这里可以对 ClassNode 对象进行更改。这使得更改字段/方法访问非常容易。再加上 ASM 的 Tree API,修改方法的字节码是轻而易举的。

编辑完成后,你可以使用以下方法将 ClassNode 转换回字节,并在 transform 方法中返回它们 :

public static byte[] getNodeBytes(ClassNode cn, boolean useMaxs) {
    ClassWriter cw = new ClassWriter(useMaxs ? ClassWriter.COMPUTE_MAXS : ClassWriter.COMPUTE_FRAMES);
    cn.accept(cw);
    byte[] b = cw.toByteArray();
    return b;
}