2009-05-30 31 views
28

Tôi cần có danh sách tất cả các phương thức người gọi cho một phương thức mà tôi quan tâm trong Java. Có một công cụ có thể giúp tôi với điều này?Làm thế nào tôi có thể tìm thấy tất cả các phương thức gọi một phương thức đã cho trong Java?

Chỉnh sửa: Tôi quên đề cập đến rằng tôi cần thực hiện việc này từ một chương trình. Tôi là Java Pathfinder và tôi muốn chạy nó tất cả các phương pháp gọi phương thức mà tôi quan tâm.

+3

Guys miklosar đã chỉnh sửa q và nói điều này cần phải xảy ra trong thời gian chạy, có vẻ như vô số người nói cách làm điều này trong một IDE. Bỏ phiếu @chadwick –

+1

có lẽ bạn có thể giải thích lý do tại sao bạn cần làm điều này trong thời gian chạy vì mọi người có thể không gặp phải yêu cầu này. –

Trả lời

39

Để phân tích bytecode, tôi khuyên bạn nên ASM. Đưa ra một danh sách các lớp để phân tích, một người truy cập có thể được thực hiện tìm thấy các cuộc gọi phương thức mà bạn quan tâm. Một triển khai thực hiện phân tích các lớp trong tệp jar bên dưới.

Lưu ý rằng ASM sử dụng internalNames bằng '/' thay vì '.' như một dấu phân cách. Chỉ định phương thức đích là standard declaration mà không có công cụ sửa đổi.

Ví dụ, để liệt kê các phương pháp có thể được gọi System.out.println ("foo") trong thời gian chạy jar java:

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)" 

Sửa: nguồn và dòng số thêm: Lưu ý rằng đây chỉ cho biết lời gọi phương thức mục tiêu cuối cùng cho mỗi phương thức gọi - q ban đầu chỉ muốn biết mà phương thức. Tôi để nó như là một bài tập cho người đọc để hiển thị số dòng của tuyên bố phương thức gọi điện thoại, hoặc số dòng của mỗi lời gọi mục tiêu, tùy thuộc vào những gì bạn đang thực sự sau. :)

kết quả trong:

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 

nguồn:

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(); 
     } 
    } 

} 
+1

Đây chính xác là những gì tôi đang tìm kiếm. Có cách nào để tìm ra dòng nguồn của các phương thức không? – arthur

+0

Với thông tin gỡ lỗi trong bình, bạn sẽ thấy các phương thức visitSource và visitLineNumber được gọi (chắc chắn không tắt thông tin DEBUG trong cuộc gọi chấp nhận, như tôi đã có). Không chắc chắn lý do tại sao đường dẫn đầy đủ đến nguồn bị thiếu, đó có thể là vấn đề về trình biên dịch hoặc có thể có chú thích cho nó. Lưu ý rằng điều này chỉ hiển thị lời gọi phương thức đích cuối cùng cho mỗi phương thức - q ban đầu của bạn chỉ muốn biết phương thức nào. Nỗ lực nhỏ sẽ hiển thị số dòng của khai báo phương thức nếu đó là những gì bạn đang theo dõi. – Chadwick

+0

Mã này chỉ trả về một cuộc gọi cho mỗi phương thức gọi; điều này có lẽ ban đầu được dự định, nhưng với việc bổ sung các số dòng, nó bây giờ có vẻ giống như một lỗi ... – daphshez

11

Chỉnh sửa: câu hỏi ban đầu đã được chỉnh sửa để cho biết giải pháp thời gian chạy là cần thiết - câu trả lời này được đưa ra trước chỉnh sửa đó và chỉ cho biết cách thực hiện trong quá trình phát triển.

Nếu bạn đang sử dụng Eclipse, bạn có thể nhấp chuột phải vào phương thức và chọn "Mở phân cấp cuộc gọi" để nhận thông tin này.

Cập nhật sau khi đọc bình luận: IDE khác hỗ trợ này cũng như trong một thời trang tương tự (ít nhất là Netbeans và IntelliJ làm)

+0

Tôi không nhớ tên chính xác, nhưng trong NetBeans nó là khá nhiều điều tương tự. –

+0

Hầu hết các IDE hỗ trợ điều này. IntelliJ cũng hỗ trợ điều này. –

+0

cảm ơn, tôi sẽ cập nhật câu trả lời của mình. –

1

Vâng, hầu hết các IDE hiện đại: s sẽ cho phép bạn hoặc tìm kiếm các tập quán của một phương pháp hoặc biến . Ngoài ra, bạn có thể sử dụng trình gỡ lỗi và đặt một điểm theo dõi trên mục nhập phương thức, in dấu vết ngăn xếp hoặc bất cứ khi nào phương thức được gọi. Cuối cùng, bạn có thể sử dụng một số vỏ đơn giản util chỉ grep cho phương pháp, chẳng hạn như

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

Phương pháp duy nhất mà sẽ cho phép bạn tìm invokations thực hiện thông qua một số phương pháp phản xạ, tuy nhiên, sẽ là bằng cách sử dụng trình gỡ lỗi.

+0

Tôi sử dụng * tìm * rất thường xuyên đến mức tôi đã có * fij * ("tìm trong java) và * sửa * (tìm trong xml) và * sửa * (tìm trong tập tin văn bản) vv bí danh nhưng ...Như vậy * find * không làm điều tương tự như IDE: nó cũng sẽ hiển thị cho bạn các chú thích bằng cách sử dụng chuỗi đó và các phương thức từ các gói khác có cùng tên, đó là một PITA chính. Vì vậy, nhiều như tôi sử dụng nó một lần trong một thời gian nó không phải là bất cứ nơi nào gần như tốt như những gì một IDE nào. – SyntaxT3rr0r

0

Gần nhất mà tôi có thể tìm thấy là phương pháp được mô tả trong câu hỏi chọn câu trả lời được chọn trong StackOverflow này. check this out

-1

Bạn có thể làm điều này với một cái gì đó trong IDE của bạn như "Tìm Usages" (đó là những gì nó được gọi là trong Netbeans và JDeveloper). Một vài điều cần lưu ý:

  1. Nếu phương pháp của bạn thực hiện một phương thức từ giao diện hoặc lớp cơ sở, bạn chỉ có thể biết rằng phương thức của bạn được gọi một cách đáng kể.
  2. Rất nhiều khung công tác Java sử dụng Reflection để gọi phương thức của bạn (IE Spring, Hibernate, JSF, v.v.), vì vậy hãy cẩn thận về điều đó.
  3. Trên cùng một lưu ý, phương pháp của bạn có thể được gọi bằng một số khung, phản ánh hay không, vì vậy hãy cẩn thận.
2

Không có cách nào để thực hiện việc này (theo chương trình) qua thư viện phản chiếu Java - bạn không thể yêu cầu java.lang.reflect.Method "bạn gọi phương thức nào?"

Vậy là hai lựa chọn khác mà tôi có thể nghĩ đến:

  1. phân tích tĩnh của mã nguồn. Tôi chắc chắn đây là những gì mà bộ công cụ Java Eclipse thực hiện - bạn có thể xem xét nguồn Eclipse đằng sau JDT, và tìm thấy những gì nó làm khi bạn yêu cầu Eclipse "Tìm tài liệu tham khảo" cho một phương thức.

  2. Phân tích Bytecode. Bạn có thể kiểm tra bytecode cho các cuộc gọi đến phương thức. Tôi không chắc chắn các thư viện hoặc ví dụ nào ở đó để giúp đỡ điều này - nhưng tôi không thể tưởng tượng rằng một cái gì đó không tồn tại.

4

Chú thích phương thức bằng @Deprecated (hoặc gắn thẻ với @deprecated), bật cảnh báo không dùng nữa, chạy trình biên dịch của bạn và xem cảnh báo nào được kích hoạt.

Việc chạy bit biên dịch của bạn có thể được thực hiện bằng cách gọi quy trình ant bên ngoài hoặc bằng cách sử dụng Java 6 compiler API.

3

Trong nhật thực, làm nổi bật tên phương pháp và sau đó Ctrl + Shift + G

5
  1. nhấp chuột phải vào phương pháp
  2. Chuyển đến tham chiếu d (tùy theo yêu cầu của bạn)
    chọn không gian làm việc/dự án/Phân cấp.

Điều này bật lên một bảng hiển thị tất cả các tham chiếu đến chức năng này. Eclipse FTW!

0

Tôi đã tạo một ví dụ nhỏ bằng cách sử dụng @ Chadwick's one. Đó là một thử nghiệm đánh giá nếu các lệnh gọi hàm getDatabaseEngine() được thực hiện bằng các phương thức thực hiện @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(); 
} 
1

1) Trong nhật thực nó là -> bấm chuột phải vào phương pháp và chọn mở hệ thống phân cấp cuộc gọi hoặc CLT+ALT+H

2) Trong JDeveloper nó là -> bấm chuột phải vào phương pháp và chọn cuộc gọi hoặc ALT+SHIFT+H

Các vấn đề liên quan