【问题标题】:How can I find all the methods that call a given method in Java?如何找到在 Java 中调用给定方法的所有方法?
【发布时间】:2010-10-30 03:21:22
【问题描述】:

我需要为我感兴趣的 Java 方法获取所有调用方方法的列表。有什么工具可以帮助我解决这个问题吗?

编辑:我忘了提到我需要从程序中执行此操作。我正在使用 Java Pathfinder,我想在所有调用我感兴趣的方法的方法中运行它。

【问题讨论】:

  • Guys miklosar 确实编辑了 q 并说这需要在运行时发生,似乎有无数人在说如何在 IDE 中执行此操作。投票@chadwick
  • 也许您可以解释为什么需要在运行时执行此操作,因为人们可能没有遇到此要求。

标签: java


【解决方案1】:

1)在eclipse中是->右击方法并选择open call hierarchy或者CLT+ALT+H

2)在 jdeveloper 中是 -> 右键单击​​方法并选择调用或ALT+SHIFT+H

【讨论】:

    【解决方案2】:

    我用@Chadwick 的例子做了一个小例子。这是一个评估对 getDatabaseEngine() 的调用是否由实现 @Transaction 的方法进行的测试。

    /**
     * Ensures that methods that call {@link DatabaseProvider#getDatabaseEngine()}
     * implement the {@link @Transaction} annotation.
     *
     * @throws Exception If something occurs while testing.
     */
    @Test
    public void ensure() throws Exception {
        final Method method = Method.getMethod(
                DatabaseEngine.class.getCanonicalName() + " getDatabaseEngine()");
    
        final ArrayList<java.lang.reflect.Method> faultyMethods = Lists.newArrayList();
    
        for (Path p : getAllClasses()) {
            try (InputStream stream = new BufferedInputStream(Files.newInputStream(p))) {
                ClassReader reader = new ClassReader(stream);
    
    
                reader.accept(new ClassAdapter(new EmptyVisitor()) {
                    @Override
                    public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
    
                        return new MethodAdapter(new EmptyVisitor()) {
                            @Override
                            public void visitMethodInsn(int opcode, String owner, String nameCode, String descCode) {
                                try {
                                    final Class<?> klass = Class.forName(Type.getObjectType(owner).getClassName());
                                    if (DatabaseProvider.class.isAssignableFrom(klass) &&
                                            nameCode.equals(method.getName()) &&
                                            descCode.equals(method.getDescriptor())) {
    
                                        final java.lang.reflect.Method method = klass.getDeclaredMethod(name,
                                                getParameters(desc).toArray(new Class[]{}));
    
                                        for (Annotation annotation : method.getDeclaredAnnotations()) {
                                            if (annotation.annotationType().equals(Transaction.class)) {
                                                return;
                                            }
                                        }
    
                                        faultyMethods.add(method);
    
                                    }
                                } catch (Exception e) {
                                    Throwables.propagate(e);
                                }
                            }
                        };
                    }
                }, 0);
    
            }
        }
    
        if (!faultyMethods.isEmpty()) {
            fail("\n\nThe following methods must implement @Transaction because they're calling getDatabaseEngine().\n\n" + Joiner.on("\n").join
                    (faultyMethods) + "\n\n");
        }
    
    }
    
    /**
     * Gets all the classes from target.
     *
     * @return The list of classes.
     * @throws IOException If something occurs while collecting those classes.
     */
    private List<Path> getAllClasses() throws IOException {
        final ImmutableList.Builder<Path> builder = new ImmutableList.Builder<>();
        Files.walkFileTree(Paths.get("target", "classes"), new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
                if (file.getFileName().toString().endsWith(".class")) {
                    builder.add(file);
                }
                return FileVisitResult.CONTINUE;
            }
        });
    
        return builder.build();
    }
    
    /**
     * Gets the list of parameters given the description.
     *
     * @param desc The method description.
     * @return The list of parameters.
     * @throws Exception If something occurs getting the parameters.
     */
    private List<Class<?>> getParameters(String desc) throws Exception {
        ImmutableList.Builder<Class<?>> obj = new ImmutableList.Builder<>();
    
        for (Type type : Type.getArgumentTypes(desc)) {
            obj.add(ClassUtils.getClass(type.getClassName()));
        }
    
        return obj.build();
    }
    

    【讨论】:

      【解决方案3】:
      1. 右键单击方法
      2. 转到参考资料和(取决于您的要求)
        选择工作区/项目/层次结构。

      这会弹出一个面板,其中显示了对该函数的所有引用。 Eclipse FTW!

      【讨论】:

        【解决方案4】:

        为了分析字节码,我推荐ASM。给定一个要分析的类列表,可以创建一个访问者来查找您感兴趣的方法调用。下面是一个分析 jar 文件中的类的实现。

        请注意,ASM 使用 internalNames 和 '/' 而不是 '.'作为分隔符。将目标方法指定为不带修饰符的standard declaration

        例如,列出可能在 java 运行时 jar 中调用 System.out.println("foo") 的方法:

        java -cp "classes;asm-3.1.jar;asm-commons-3.1.jar" App \
            c:/java/jdk/jre/lib/rt.jar \
            java/io/PrintStream  "void println(String)"
        

        编辑:添加源代码和行号:请注意,这仅表示每个调用方法的最后一次目标方法调用 - 原始 q 只想知道 哪些 方法。我将其作为练习留给读者,以显示调用方法声明的行号,或每个目标调用的行号,具体取决于您实际追求的内容。 :)

        结果:

        LogSupport.java:44 com/sun/activation/registries/LogSupport log (Ljava/lang/String;)V
        LogSupport.java:50 com/sun/activation/registries/LogSupport log (Ljava/lang/String;Ljava/lang/Throwable;)V
        ...
        Throwable.java:498 java/lang/Throwable printStackTraceAsCause (Ljava/io/PrintStream;[Ljava/lang/StackTraceElement;)V
        --
        885 methods invoke java/io/PrintStream println (Ljava/lang/String;)V
        

        来源:

        public class App {
            private String targetClass;
            private Method targetMethod;
        
            private AppClassVisitor cv;
        
            private ArrayList<Callee> callees = new ArrayList<Callee>();
        
            private static class Callee {
                String className;
                String methodName;
                String methodDesc;
                String source;
                int line;
        
                public Callee(String cName, String mName, String mDesc, String src, int ln) {
                    className = cName; methodName = mName; methodDesc = mDesc; source = src; line = ln;
                }
            }
        
            private class AppMethodVisitor extends MethodAdapter {
        
                boolean callsTarget;
                int line;
        
                public AppMethodVisitor() { super(new EmptyVisitor()); }
        
                public void visitMethodInsn(int opcode, String owner, String name, String desc) {
                    if (owner.equals(targetClass)
                            && name.equals(targetMethod.getName())
                            && desc.equals(targetMethod.getDescriptor())) {
                        callsTarget = true;
                    }
                }
        
                public void visitCode() {
                    callsTarget = false;
                }
        
                public void visitLineNumber(int line, Label start) {
                    this.line = line;
                }
        
                public void visitEnd() {
                    if (callsTarget)
                        callees.add(new Callee(cv.className, cv.methodName, cv.methodDesc, 
                                cv.source, line));
                }
            }
        
            private class AppClassVisitor extends ClassAdapter {
        
                private AppMethodVisitor mv = new AppMethodVisitor();
        
                public String source;
                public String className;
                public String methodName;
                public String methodDesc;
        
                public AppClassVisitor() { super(new EmptyVisitor()); }
        
                public void visit(int version, int access, String name,
                                  String signature, String superName, String[] interfaces) {
                    className = name;
                }
        
                public void visitSource(String source, String debug) {
                    this.source = source;
                }
        
                public MethodVisitor visitMethod(int access, String name, 
                                                 String desc, String signature,
                                                 String[] exceptions) {
                    methodName = name;
                    methodDesc = desc;
        
                    return mv;
                }
            }
        
        
            public void findCallingMethodsInJar(String jarPath, String targetClass,
                                                String targetMethodDeclaration) throws Exception {
        
                this.targetClass = targetClass;
                this.targetMethod = Method.getMethod(targetMethodDeclaration);
        
                this.cv = new AppClassVisitor();
        
                JarFile jarFile = new JarFile(jarPath);
                Enumeration<JarEntry> entries = jarFile.entries();
        
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
        
                    if (entry.getName().endsWith(".class")) {
                        InputStream stream = new BufferedInputStream(jarFile.getInputStream(entry), 1024);
                        ClassReader reader = new ClassReader(stream);
        
                        reader.accept(cv, 0);
        
                        stream.close();
                    }
                }
            }
        
        
            public static void main( String[] args ) {
                try {
                    App app = new App();
        
                    app.findCallingMethodsInJar(args[0], args[1], args[2]);
        
                    for (Callee c : app.callees) {
                        System.out.println(c.source+":"+c.line+" "+c.className+" "+c.methodName+" "+c.methodDesc);
                    }
        
                    System.out.println("--\n"+app.callees.size()+" methods invoke "+
                            app.targetClass+" "+
                            app.targetMethod.getName()+" "+app.targetMethod.getDescriptor());
                } catch(Exception x) {
                    x.printStackTrace();
                }
            }
        
        }
        

        【讨论】:

        • 这正是我正在寻找的。有什么办法也可以找到方法的源代码行吗?
        • 使用 jar 中的调试信息,您将看到调用的 visitSource 和 visitLineNumber 方法(请确保不要像我最初那样在接受调用中禁用 DEBUG 信息)。不确定为什么缺少源的完整路径,这可能是编译器问题,或者可能有注释。请注意,这仅显示每个方法的最后一个目标方法调用 - 您原来的 q 只想知道哪些方法。如果这就是你所追求的,那么小的努力就会显示方法声明的行号。
        • 这段代码每个调用方法只返回一个调用;这可能是原本打算的,但是随着行号的添加,现在看起来更像是一个错误......
        • @Daphna Shezaf 我在您之前的评论中强调了这一事实,但我将编辑上面的答案以包含该警告。我把它作为练习留给读者为任何实际情况定制这个示例代码:)
        • 对此回复+1。刚刚将其修改为一个速度惊人的自定义交叉引用工具。谢谢!
        【解决方案5】:

        编辑:对原始问题进行了编辑以表明需要运行时解决方案 - 此答案是在编辑之前给出的,仅说明如何在开发过程中进行。

        如果您使用的是 Eclipse,您可以右键单击该方法并选择“打开调用层次结构”以获取此信息。

        阅读 cmets 后更新:其他 IDE 也以类似的方式支持此功能(至少 Netbeans 和 IntelliJ 支持)

        【讨论】:

        • 我不记得确切的名称,但在 NetBeans 中它几乎是一回事。
        • 大多数 IDE 都支持这一点。 IntelliJ 也支持这一点。
        【解决方案6】:

        在eclipse中,高亮方法名,然后Ctrl+Shift+G

        【讨论】:

          【解决方案7】:

          使用 @Deprecated 注释方法(或使用 @deprecated 标记),打开弃用警告,运行编译并查看触发了哪些警告。

          可以通过调用外部 ant 进程使用Java 6 compiler API来运行您的编译位。

          【讨论】:

            【解决方案8】:

            没有办法通过 Java 反射库(以编程方式)做到这一点 - 你不能问java.lang.reflect.Method“你调用了哪些方法?”

            剩下两个我能想到的选择:

            1. 源代码的静态分析。我确信这就是 Eclipse Java 工具集的作用——您可以查看 JDT 背后的 Eclipse 源代码,并在您要求 Eclipse“查找引用”某个方法时找到它的作用。

            2. 字节码分析。您可以检查字节码以查找对该方法的调用。我不确定有哪些库或示例可以帮助解决这个问题 - 但我无法想象某些东西不存在。

            【讨论】:

              【解决方案9】:

              您可以在 IDE 中使用“查找用法”(Netbeans 和 JDeveloper 中的名称)等内容来执行此操作。有几点需要注意:

              1. 如果您的方法从接口或基类实现方法,您只能知道您的方法可能被调用。
              2. 很多 Java 框架都使用反射来调用您的方法(IE Spring、Hibernate、JSF 等),所以要小心。
              3. 同样,您的方法可能会被某些框架调用,无论是否反射,所以再次小心。

              【讨论】:

                【解决方案10】:

                我能找到的最接近的是这个 StackOverflow questions selected answer中描述的方法。check this out

                【讨论】:

                  【解决方案11】:

                  是的,大多数现代 IDE:s 都可以让您搜索方法或变量的用法。或者,您可以使用调试器并在方法条目上设置跟踪点,在每次调用方法时打印堆栈跟踪或其他任何内容。 最后,您可以使用一些简单的 shell 工具来 grep 方法,例如

                  find . -name '*.java' -exec grep -H methodName {} ;
                  

                  不过,唯一能让您找到通过某种反射方法进行的调用的方法是使用调试器。

                  【讨论】:

                  • 我经常使用这样的 find,以至于我得到了 fij ("find in java) 和 fix ( find in xml) 和 fix (find in text files) 等别名,但是......这样的 find 与 IDE 做的事情不同:它还应显示您使用该字符串和其他软件包中的方法来使用该字符串和方法,这些软件包恰好具有相同的名称,这是一个主要的 PITA。因此,尽管我偶尔使用它,但它并不像 IDE 那样好。跨度>
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-06-04
                  • 1970-01-01
                  • 1970-01-01
                  • 2023-03-09
                  • 1970-01-01
                  相关资源
                  最近更新 更多