Javassist:如何将代理添加到类路径?

Javassist: How to add agent to classpath?

我有一个动态加载到 运行 Java 应用程序的代理程序,它在附加时打开一个简单的 Swing JFrame。它还允许将新行附加到该 JFrame 内的 TextArea。

我的目标是改变某些方法在加载代理的应用程序中的工作方式。

public class MyAgent {
    public static void agentmain(String args, Instrumentation instrumentation) {
        UI.openWindow();
        UI.addMessage("Agent loaded: %s", args);
        instrumentation.addTransformer(new MyTransformer());
        instrumentation.redefineClasses(new ClassDefinition(Class.forName("app.TargetClass"), ...));
    }
}

UI window 在另一个可从代理访问的 class 中进行管理。它成功打开 window 并在加载代理时附加一条文本消息。

public class UI {
    private static SwingWindow swingWindow;
    
    public static void addMessage(String format, Object... args) {
        System.out.println("UI: " + String.format(format, args));
        swingWindow.appendToTextArea(format, args);
    }
    
    public static void openWindow() {
        try {
            SwingUtilities.invokeAndWait(() -> swingWindow = new SwingWindow());
        }
        catch (Exception e) {}
    }
}

我正在使用 Javassist 在我的转换器中生成字节码。

public class MyTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, ..., byte[] classBuffer) {
        if (className.equals("app/TargetClass")) {
            UI.addMessage("Now transforming the class that I need!");

            try {
                ClassPool classPool = ClassPool.getDefault();
                CtClass targetClass = classPool.get("app.TargetClass");
                CtMethod targetMethod = targetClass.getDeclaredMethod("importantMethod");
                targetMethod.insertBefore("me.domain.agent.ui.UI.addMessage(\"Hello from Javassist!\")");
                byte[] byteCode = targetClass.toBytecode();
                targetClass.detach();
                return byteCode;
            }
            catch (Exception e) {
                UI.addMessage("Couldn't transform the class I needed.");
            }
        }

        return classBuffer;
    }
}

找到目标 class,但字节码未编译:

UI: Failed transforming class app.TargetClass: [source error] no such class: me$domain.agent.ui.UI

然而,UIclass在代理里面:

agent.jar
├── META-INF
└── me.domain.agent
    ├── ui
    │   └── UI.class
    ├── MyTransformer.class
    └── Agent.class

我试过将代理的 ClassLoader 添加到 Javassist 的 ClassPool:

classPool.insertClassPath(new LoaderClassPath(Agent.class.getClassLoader()));

但是没有用。如何将对代理 UI 的调用添加到字节码中?

我决定使用 ASM 从字节码调用我的代理 UI。找到 class.

没有问题

以下是基于 ASM 的 class 变压器的外观:

public class MyTransformer implements ClassFileTransformer {
    public void transformClass(ClassNode classNode) {
        MethodNode methodNode = findMethodNodeOfClass(classNode, "importantMethod", "()V");
        if (methodNode == null) {
            throw new TransformerException("app.TargetClass#importantMethod not found");
        }

        AbstractInsnNode firstInsn = findFirstInstruction(methodNode);
        if (firstInsn == null) {
            throw new TransformerException("No instructions in app.TargetClass#importantMethod");
        }

        InsnList insnList = new InsnList();
        insnList.add(new LdcInsnNode("Hello from ASM!"));
        insnList.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(UI.class), "addMessage", "(Ljava/lang/String;)V"));
        methodNode.instructions.insertBefore(firstInsn, insnList);
    }

    @Override
    public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] byteCode) {
        if (className.equals("app/TargetClass")) {
            try {
                ClassNode classNode = new ClassNode();
                ClassReader classReader = new ClassReader(byteCode);
                classReader.accept(classNode, 0);
                this.transformClass(classNode);
                ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
                classNode.accept(classWriter);
                return classWriter.toByteArray();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }

        return byteCode;
    }
}

这将在 app.TargetClass#importantMethod 的第一条非标签指令之前插入静态调用 UI.addMessage("Hello from ASM!")