如何获取 Java 源代码的完整调用层次结构?

2022-09-04 08:00:46

这有点难以解释。我有一个A类:

public class A {
    private Integer a1;
    private Integer a2;
    // getters and setters.
}

有一个静态类 B 返回我的类 A:

public static class B {
    public static A getCurrentA() {
        return a;
    }
}

我需要找到 B 返回的类 A 的所有用法。因此,假设类C调用,然后进一步有一个调用,我想找到所有这些。c.setA(B.getCurrentA())c.getA().getA2();

在实际场景中,我有 217 个不同的类调用 .我无法手动跟踪 Eclipse 中的所有调用并找出哪些方法被调用。B.getCurrentA()

Eclipse 调用层次结构视图仅显示对 的所有调用。B.getCurrentA()

我怎样才能做到这一点?


编辑

克里斯·海耶斯明白我想做什么。为了重构一些非常糟糕的遗留代码而不会破坏整个系统,我需要首先使用Hibernate的投影微调一些查询(系统中的每个映射实体都急切地加载,并且许多实体是相关的,所以有些查询需要很长时间才能获取所有内容)。但首先,我需要找到使用了哪些属性,这样我就不会在某个地方得到NullPointerException......

以下是我必须手动执行的操作的示例:

  1. 使用 Eclipse 的 Search 查找对 B.getCurrentA() 的所有调用;
  2. 打开找到的第一种方法,假设它是下面的方法:

    public class CController {
        C c = new C();
        CFacade facade = new CFacade();
        List<C> Cs = new ArrayList<C>();
    
        public void getAllCs() {
            c.setA(B.getCurrentA()); // found it!
            facade.search(c);
        }
    }
    
  3. 在 CFacade 类中打开搜索方法:

    public class CFacade {
        CBusinessObject cBo = new CBusinessObject();
    
        public List<C> search(C c) {
            // doing stuff...
            cBo.verifyA(c);
            cBo.search(c); // yes, the system is that complicated
        }
    }
    
  4. 打开 CBusinessObject 类中的 verifyA 方法,并确定使用了字段 a2:

    public class CBusinessObject {
        public void verifyA(c) {
            if (Integer.valueOf(1).equals(c.getA().getA2())) {
                // do stuff
            else {
                // something else
            }
        }
    }
    
  5. 在接下来的 216 场比赛中重复步骤 2-4...耶。

请帮忙。


答案 1

如果你想进行任何源代码更改/重构,你将不得不手动查找所有用法并应用代码更改;

无论如何,我有两个不同的aproach

  1. 静态搜索 您只需在 eclipse 中查找 的出现情况即可。它将直接将您带到调用方方法(此处为CBusinessObject.verifyA()) - 但它将为您提供每个getA2()出现,可能来自不同的类Text SearchgetA2()

  2. 运行时搜索 用于在运行时更改所需方法的字节代码以查找调用类并运行为 - 使您能够在不接触现有代码库的情况下识别调用方,这非常有用,尤其是当您无权访问源代码时。java instrumentation APIjava agent

在这里,你如何实现

步骤 1 - 编写代理主类以启动检测

public class BasicAgent {
                public static void premain(String agentArguments, Instrumentation instrumentation){
                    System.out.println("Simple Agent");
                    FindUsageTransformer transformer = new FindUsageTransformer ();
                    instrumentation.addTransformer(transformer,true);
                }
            }

步骤 2 - 编写类文件转换器实现并捕获方法

public class FindUsageTransformer implements ClassFileTransformer{

        Class clazz = null;
        public byte[] transform(ClassLoader loader,String className,Class<?>  classBeingRedefined,  ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer)    throws IllegalClassFormatException {
            if(className.equals("A")){
                doClass(className, classBeingRedefined, classfileBuffer);
            }
            return classfileBuffer;
        }
        private byte[] doClass(String name, Class clazz, byte[] b) {
            ClassPool pool = ClassPool.getDefault();
            CtClass cl = null;
            try {
              cl = pool.makeClass(new java.io.ByteArrayInputStream(b));
              CtMethod method =  cl.getDeclaredMethod("getA2");
              // here you have lot of options to explore
              method.insertBefore("System.out.println(Thread.currentThread().getStackTrace()[0].getClassName()+ Thread.currentThread().getStackTrace()[0].getMethodName());");
              b = cl.toBytecode();
            } catch (Exception e) {
              System.err.println("Could not instrument  " + name
                  + ",  exception : " + e.getMessage());
            } finally {
              if (cl != null) {
                cl.detach();
              }
            }
            return b;
          }

步骤3-为代理类创建jar文件(您必须使用premain类设置清单文件,并添加javaassit jar)给出了构建文件的片段 - 您也可以手动完成

<jar destfile="build/jar/BasicAgent.jar" basedir="build/classes">
                <manifest>
                    <attribute name="Manifest-Version" value="1.0"/>
                    <attribute name="Premain-Class" value="com.sk.agent.basic.BasicAgent"/>
                    <attribute name="Boot-Class-Path" value="../lib/javassist.jar"/>
                </manifest>
            </jar>

步骤 4 - 使用 java 代理运行主应用程序 - 在设置 VM 参数以加载代理之前

            -`javaagent:D:\softwares\AgentProject\AgentLib\build\jar\BasicAgent.jar`

先决条件:在类路径中需要javassist.jar


答案 2

根据您使用的IDE,此问题更容易找到。

Eclipse IDE 具有现存最有潜力的调用层次结构模块之一,您只需将鼠标放在要查找和执行的方法声明中,这将为您提供哪个方法正在使用要分析的方法的整个层次结构。Ctrl + Alt + H

此外,“调用层次结构”模块还提供了一种模式,您可以在其中找到方法正在调用的方法。

一些额外的信息: http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.cdt.doc.user%2Freference%2Fcdt_u_call_hierarchy_view.htm


推荐