提问者:小点点

转换类没有效果


根据本教程,我尝试让java代理工作。https://www.baeldung.com/java-instrumentation#loading-a-java-agent

我确实得到了[Agent]转换类TestApplication我没有错误,但是我看不到转换类的任何效果。

最终我想让静态负载和动态负载都工作,但现在我专注于静态方式。


public class Static_Agent {

    public static void premain(String agentArgs, Instrumentation inst) {
        String[] tokens = agentArgs.split(";");
        String className = tokens[0];
        String methodName = tokens[1];

        System.out.println(">> "+className);
        System.out.println(">> "+methodName);
        transformClass(className, methodName, inst);
    }



    public static void transformClass(String className, String methodName, Instrumentation instrumentation) {
        Class<?> targetCls = null;
        ClassLoader targetClassLoader = null;
        // see if we can get the class using forName
        try {
            targetCls = Class.forName(className);
            targetClassLoader = targetCls.getClassLoader();
            transform(targetCls, methodName, targetClassLoader, instrumentation);
            return;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        // otherwise iterate all loaded classes and find what we want
        for(Class<?> clazz: instrumentation.getAllLoadedClasses()) {
            if(clazz.getName().equals(className)) {
                targetCls = clazz;
                targetClassLoader = targetCls.getClassLoader();
                transform(targetCls, methodName, targetClassLoader, instrumentation);
                return;
            }
        }
        throw new RuntimeException("Failed to find class [" + className + "]");
    }


    public static void transform(Class<?> clazz, String methodName, ClassLoader classLoader, Instrumentation instrumentation) {
        Transformer dt = new Transformer(clazz.getName(), methodName, classLoader);
        instrumentation.addTransformer(dt, true);
        try {
            instrumentation.retransformClasses(clazz);
        } catch (Exception ex) {
            throw new RuntimeException("Transform failed for class: [" + clazz.getName() + "]", ex);
        }
    }



}
public class Transformer implements ClassFileTransformer {


    /** The internal form class name of the class to transform */
    private String targetClassName;
    /** The class loader of the class we want to transform */
    private ClassLoader targetClassLoader;

    private String targetMethodName;

    public Transformer(String targetClassName, String targetMethodName, ClassLoader targetClassLoader) {
        this.targetClassName = targetClassName;
        this.targetClassLoader = targetClassLoader;
        this.targetMethodName = targetMethodName;
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        byte[] byteCode = classfileBuffer;

        String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/");
        if (!className.equals(finalTargetClassName)) {
            return byteCode;
        }

        if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
            System.out.println("[Agent] Transforming class TestApplication");
            try {

                ClassPool cp = ClassPool.getDefault();
                CtClass cc = cp.get(targetClassName);
                CtMethod m = cc.getDeclaredMethod(targetMethodName);
                m.addLocalVariable("startTime", CtClass.longType);
                m.insertBefore("startTime = System.currentTimeMillis();");

                StringBuilder endBlock = new StringBuilder();

                m.addLocalVariable("endTime", CtClass.longType);
                m.addLocalVariable("opTime", CtClass.longType);
                endBlock.append("endTime = System.currentTimeMillis();");
                endBlock.append("opTime = (endTime-startTime)/1000;");

                endBlock.append("System.out.println(\"[Application] Withdrawal operation completed in:\" + opTime + \" seconds!\");");

                m.insertAfter(endBlock.toString());

                byteCode = cc.toBytecode();
                cc.detach();
            } catch (Exception e) {
                System.out.println("Exception"+e);
            }
        }
        return byteCode;
    }
}
public class TestApplication {

    public static void main(String[] args) {

        try {
            TestApplication.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void run() throws Exception {
        System.out.println("--- start ---");

        while (true) {
            test();
            Thread.sleep(4_000);
        }


    }


    static int count = 0;

    public static void test() {
        System.out.println(count++);
    }

}

我启动:

java-javaagent:static_agent. jar="doeke.application.TestApplication;test"-jarapplication.jar

如果有帮助,项目在这里:https://github.com/clankill3r/java_agent

编辑:

在文件末尾的Transformer.java中,我现在使用e. printStackTrace();

我收到以下错误:

---启动---

0

1


共1个答案

匿名用户

谢谢你提出这个问题,让我有机会看看Java仪器。

在花了一些时间交叉检查您的示例代码和提供的教程之后。问题不在于编程代码,而在于如何启动您的程序。

如果你在Transformer.java中的Transfer()方法中添加一些记录器,你会发现运行后代码路径被破坏了:

ClassPool cp = ClassPool.getDefault();

并且,在从以下相同方法替换异常捕获代码后:

} catch (Exception e) {

到:

} catch (NotFoundException | CannotCompileException | IOException e) {

它会给你更多的提示如下:

Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)
        at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(Unknown Source)
Caused by: java.lang.NoClassDefFoundError: javassist/NotFoundException
        at doeke.static_agent.Static_Agent.transform(Static_Agent.java:60)
        at doeke.static_agent.Static_Agent.transformClass(Static_Agent.java:40)
        at doeke.static_agent.Static_Agent.premain(Static_Agent.java:28)
        ... 6 more
Caused by: java.lang.ClassNotFoundException: javassist.NotFoundException
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 9 more
FATAL ERROR in native method: processing of -javaagent failed

到目前为止,根本原因更加明显。这是因为在启动程序时,那些jav辅助相关的类(例如ClassPool、CtClass、CtMethod等)在运行时无法引用其对应的库。

所以,解决方案是:

>

  • 假设您已将static_agent. jar导出到与application.jar相同的"build"文件夹中

    所有其他文件夹结构与您提供的github中显示的相同

    让我们“cd”到命令控制台中的构建文件夹

    修改原节目启动脚本如下

    WindowsOS:

    java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar;application.jar doeke.application.TestApplication
    

    Unix/LinuxOS:

    java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar:application.jar doeke.application.TestApplication
    

    您最终会得到预期的结果:

    [Agent] In premain method.
    >> doeke.application.TestApplication
    >> test
    [Agent] Transforming class
    --- start ---
    0
    [Application] Withdrawal operation completed in:0 seconds!
    1
    [Application] Withdrawal operation completed in:0 seconds!
    

    编辑

    另外,让我粘贴一些关于如何通过jav辅助在方法中间插入代码的代码。

    如果TestApplication.java中的test()方法更改为:

    line 30    public static void test() {
    line 31        System.out.println(count++);
    line 32        
    line 33        System.out.println("Last line of test() method");
    line 34    }
    

    假设我们想在计数和 =========, 之间添加一行,假设“This is line分隔符”,结果如下所示:

    1 
    -- This is line separator -- 
    Last line of test() method
    

    然后,在Transformer.java方法中,您可以添加如下代码行:

    m.insertAt(32,"System.out.println(\"-- This is line separator --\");");
    

    这使得它成为:

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        byte[] byteCode = classfileBuffer;
    
        String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/");
        if (!className.equals(finalTargetClassName)) {
            return byteCode;
        }
    
        if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
            System.out.println("[Agent] Transforming class TestApplication");
            try {
                // Step 1 Preparation
                ClassPool cp = ClassPool.getDefault();
                CtClass cc = cp.get(targetClassName);
                CtMethod m = cc.getDeclaredMethod(targetMethodName);
    
                // Step 2 Declare variables
                m.addLocalVariable("startTime", CtClass.longType);
                m.addLocalVariable("endTime", CtClass.longType);
                m.addLocalVariable("opTime", CtClass.longType);
    
                // Step 3 Insertion of extra logics/implementation
                m.insertBefore("startTime = System.currentTimeMillis();");
    
                m.insertAt(32,"System.out.println(\"-- This is line separator --\");");
    
                StringBuilder endBlock = new StringBuilder();
    
                endBlock.append("endTime = System.currentTimeMillis();");
                endBlock.append("opTime = (endTime-startTime)/1000;");
                endBlock.append("System.out.println(\"[Application] Withdrawal operation completed in:\" + opTime + \" seconds!\");");
    
                m.insertAfter(endBlock.toString());
    
                // Step 4 Detach from ClassPool and clean up stuff
                byteCode = cc.toBytecode();
                cc.detach();
            } catch (NotFoundException | CannotCompileException | IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
        return byteCode;
    }
    

    最后,会得到如下在方法中间打印代码的结果:

    [Agent] In premain method.
    className=doeke.application.TestApplication
    methodName=test
    >> doeke.application.TestApplication
    >> test
    [Agent] Transforming class TestApplication
    --- start ---
    0
    -- This is line separator --
    =========
    [Application] Withdrawal operation completed in:0 seconds!
    1
    -- This is line separator --
    =========
    [Application] Withdrawal operation completed in:0 seconds!
    2
    -- This is line separator --
    =========
    [Application] Withdrawal operation completed in:0 seconds!