如何在Java中跟踪方法结果

如何在Java中跟踪方法结果,java,reflection,proxy-pattern,Java,Reflection,Proxy Pattern,简单地说,我正在开发一个系统,它能够为您提供有关java程序执行结果的信息。我已经考虑了以下问题,我不知道是否可以用java解决它 我有以下课程: public class ClassA { ClassB classB= new ClassB(); public Integer method1(){ return classB.method2(); } } public class ClassB { ClassC classC = new Clas

简单地说,我正在开发一个系统,它能够为您提供有关java程序执行结果的信息。我已经考虑了以下问题,我不知道是否可以用java解决它

我有以下课程:

public class ClassA {
    ClassB classB= new ClassB();
    public Integer method1(){
       return classB.method2();
    }
}

public class ClassB {
    ClassC classC = new ClassC();
    public Integer method2() {
        return this.classC.method3() + this.classC.method4();
    }
}

public class ClassC {
    public Integer method3() {
        return 3;
    }
    public Integer method4() {
        return 4;
    }
}
到目前为止,我可以通过使用动态代理捕获方法的每次调用。特别是,我正在使用包java.lang.reflect中的代理和InvocationHandler对象。下面是我遵循的示例()

我的问题是,如果有人知道我如何提供以下信息:
“method1()的返回由method2()的返回生成,method2()的返回由method3()的返回和method4()的返回依次生成。”

我想到的一件事是检索线程的堆栈跟踪,看看你能用它做些什么

您可以使用例如
Thread.currentThread().getStackTrace()
方法来执行此操作,并接收一个数组,最后使用
getMethodName()
方法来接收方法名


这只是一个想法,我不确定你是否能得到你想要的信息。

你需要利用ThreadLocals。您的线程本地将有一个映射,该映射将由每个方法填充。下面是一个示例代码:

public static void main(String... args){
        try{
            new ClassA().method1();

            TrackingThreadLocal.tracker.get().entrySet().stream().forEach( (e) -> System.out.println( 
            "the return of " + e.getKey() + " is generated from the return of " + e.getValue().stream().collect( Collectors.joining(", ") ) ) );

        }finally{
            //make sut to clean up to avoid ThreadLocal memoty leak
            TrackingThreadLocal.tracker.remove();
        }
    }

    public class TrackingThreadLocal{

        public static ThreadLocal< Map<String, List<String> > > tracker = new ThreadLocal< Map< String, List<String> > >(){
            @Override 
            public Map< String, List<String> > initialValue() {
                return new HashMap<>();
            }
        };
    }

    public class ClassA {
        ClassB classB= new ClassB();
        public Integer method1(){
           TrackingThreadLocal.tracker.get().put("method1", Arrays.asList("method2") );
           return classB.method2();
        }
    }

    public class ClassB {
        ClassC classC = new ClassC();
        public Integer method2() {
            TrackingThreadLocal.tracker.get().put( "method2",  Arrays.asList("method3", "method4") );
            return this.classC.method3() + this.classC.method4();
        }
    }

    public class ClassC {
        public Integer method3() {
            return 3;
        }
        public Integer method4() {
            return 4;
        }
    }
publicstaticvoidmain(字符串…参数){
试一试{
新的ClassA().method1();
TrackingThreadLocal.tracker.get().entrySet().stream().forEach((e)->System.out.println(
“+e.getKey()+”的返回是从“+e.getValue().stream().collect(Collectors.joining(“,”)的返回中生成的);
}最后{
//将sut清理干净,避免螺纹局部漏丝
TrackingThreadLocal.tracker.remove();
}
}
公共类跟踪线程本地{
public static ThreadLocaltracker=new ThreadLocal>(){
@凌驾
公共映射<字符串,列表>初始值(){
返回新的HashMap();
}
};
}
甲级公共课{
ClassB ClassB=新的ClassB();
公共整数方法1(){
TrackingThreadLocal.tracker.get().put(“method1”,Arrays.asList(“method2”);
返回classB.method2();
}
}
公共B类{
ClassC ClassC=新的ClassC();
公共整数方法2(){
TrackingThreadLocal.tracker.get().put(“method2”,Arrays.asList(“method3”,“method4”);
返回this.classC.method3()+this.classC.method4();
}
}
公共类C类{
公共整数方法3(){
返回3;
}
公共整数方法4(){
返回4;
}
}

我以前使用过仪器来解决类似的问题

免责声明:只有在控制JVM并且可以指定它与javaagent一起运行时,才能执行以下操作

代理的实现相当简单,您所需要的只是一个类,它实现了一个带有
premain(String,java.lang.Instrumentation)
签名的方法。例如:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrumentation.ClassFileTransformer;
import java.lang.instrumentation.IllegalClassFormatException;
import java.lang.instrumentation.Instrumentation;
import java.security.ProtectionDomain;

public class MyAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new ClassTransformer() {
            public byte[] transform(ClassLoader loader, String className,
                    Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                    byte[] classfileBuffer) throws IllegalClassFormatException {
            // if you want to target specific classes or packages you can filter
            // on the class name, just remember that it is the JVM class format string
            if(className.startWith("com/my/package/SomeThing")) {
                // javassist provides methods to access classes and generate bytecode
                ClassPool cp = ClassPool.getDefault();
                // you can access the class with the following
                CtClass cc = cp.get("com.my.package.MyClass$InnerClass");
                // access specific methods with
                CtMethod m = cc.getDeclaredMethod("someMethod");
                m.setBody("{MyCallTree tree = com.my.package.TreeSingleton.getTree();\ntree.addCall(" + m.getLongName + ");\n}");
                return cc.toByteCode();
            }
            else {
                // return null so that you don't break methods or classes you
                // don't want to
                return null;
            }
        });
    }
}
导入javassist.ClassPool;
导入javassist.CtClass;
导入javassist.CtMethod;
导入java.lang.instrumentation.ClassFileTransformer;
导入java.lang.instrumentation.IllegalClassFormatException;
导入java.lang.instrumentation.instrumentation;
导入java.security.ProtectionDomain;
公共类MyAgent{
公共静态无效预输入(字符串代理、仪器仪表){
inst.addTransformer(新类Transformer(){
公共字节[]转换(类加载器、字符串类名称、,
类ClassBeingRefined,ProtectionDomain ProtectionDomain,
字节[]classfileBuffer)引发IllegalClassFormatException{
//如果您想针对特定的类或包,可以进行筛选
//在类名上,只需记住它是JVM类格式字符串
if(className.startWith(“com/my/package/SomeThing”)){
//javassist提供了访问类和生成字节码的方法
ClassPool cp=ClassPool.getDefault();
//您可以使用以下命令访问该类
CtClass cc=cp.get(“com.my.package.MyClass$InnerClass”);
//使用
CTM方法=cc.getDeclaredMethod(“someMethod”);
m、 setBody(“{MyCallTree-tree=com.my.package.TreeSingleton.getTree();\ntree.addCall(“+m.getLongName+”);\n}”);
返回cc.toByteCode();
}
否则{
//返回null,这样您就不会破坏您需要的方法或类
//不想
返回null;
}
});
}
}
在上面的代码片段中,我用作为字符串传递给
CtMethod.setBody
的代码替换了整个方法体。但是,您几乎可以使用javassist、前置代码、追加代码等执行任何操作。可以找到有关使用javassist的详细教程。它是一个非常强大的库

您如何实现代码以建立呼叫信息的详细信息实际上只是Java代码,可能先将代码作为项目的一部分编写,然后使用
premain
方法中的步法。您甚至可以让它编写他在回答中建议的代码


下一步是将上述代码打包到jar文件中,然后在启动JVM时将其注入JVM,如回答中所述。

有一个调试器api和其他相关的低级api。您可以从程序本身运行的运行时内部执行的操作相当有限。另一个选项是,如果您可以控制JVM,是指令插入(
java.lang.instrumentation
)。加载类时,可以插入