2009-05-30 14 views
28

Necesito obtener una lista de todos los métodos de llamada para un método de interés para mí en Java. ¿Hay alguna herramienta que pueda ayudarme con esto?¿Cómo puedo encontrar todos los métodos que llaman a un método determinado en Java?

Editar: Olvidé mencionar que tengo que hacer esto desde un programa. Estoy usando Java Pathfinder y quiero ejecutarlo con todos los métodos que llaman mi método de interés.

+3

Chicos miklosar editó la q y dice que esto tiene que ocurrir en el tiempo de ejecución, parece que hay innumerables personas que dicen cómo hacer esto en un IDE. Hasta vote @chadwick –

+1

quizás podría explicar por qué necesita hacer esto en tiempo de ejecución ya que las personas pueden no haber encontrado este requisito. –

Respuesta

39

Para analizar bytecode, recomendaría ASM. Dada una lista de Clases para analizar, se puede crear un visitante que encuentre las llamadas a los métodos que le interesan. A continuación se incluye una implementación que analiza las clases en un archivo jar.

Tenga en cuenta que ASM usa internalNames con '/' en lugar de '.' como un separador Especifique el método de destino como standard declaration sin modificadores.

Por ejemplo, para listar los métodos que podrían estar llamando System.out.println ("foo") en el frasco de ejecución de 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)" 

Edición: números de fuente y línea ha añadido: Tenga en cuenta que este solo indica la última invocación al método de destino por método de llamada: el q original solo quería saber qué métodos de. Lo dejo como un ejercicio para que el lector muestre los números de línea de la declaración del método de llamada, o los números de línea de cada invocación de destino, dependiendo de lo que realmente está buscando. :)

resultados en:

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 

fuente:

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

Esto es exactamente lo que estoy buscando. ¿Hay alguna forma de encontrar también la línea fuente de los métodos? – arthur

+0

Con información de depuración en el contenedor, verá los métodos visitSource y visitLineNumber invocados (asegúrese de no deshabilitar la información de DEPURACIÓN en la llamada de aceptación, como originalmente tuve). No estoy seguro de por qué falta la ruta completa a la fuente, que puede ser un problema del compilador, o tal vez haya una anotación. Tenga en cuenta que esto solo muestra la última invocación de método de destino por método: su q original solo quería saber qué métodos. Un esfuerzo menor mostraría el número de línea de la declaración del método si eso es lo que está buscando. – Chadwick

+0

Este código solo devuelve una llamada por cada método de llamada; esto probablemente fue originalmente intencionado, pero con la adición de los números de línea, ahora parece más un error ... – daphshez

11

Editar: la pregunta original se editó para indicar que se necesitaba una solución en tiempo de ejecución; esta respuesta se dio antes de esa edición y solo indica cómo hacerlo durante el desarrollo.

Si está utilizando Eclipse, puede hacer clic con el botón derecho en el método y seleccionar "Abrir jerarquía de llamadas" para obtener esta información.

Actualizado después de leer los comentarios: Otros entornos de desarrollo compatibles con esta así de una manera similar (al menos Netbeans y IntelliJ hacen)

+0

No recuerdo el nombre exacto, pero en NetBeans es más o menos lo mismo. –

+0

La mayoría de los IDEs admiten esto. IntelliJ también lo admite. –

+0

gracias, actualizaré mi respuesta. –

1

Sí, más modernos IDE: s le permitirá ya sea la búsqueda de usos de un método o variable . Alternativamente, puede usar un depurador y establecer un punto de rastreo en la entrada del método, imprimiendo un seguimiento de pila o lo que sea cada vez que se invoca el método. Por último, se podría utilizar un poco de cáscara sencilla util a solo grep para el método, como

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

El único método que le permitirá encontrar invokations hechas a través de algún método de reflexión, sin embargo, sería utilizando el depurador.

+0

Utilizo ese * find * con tanta frecuencia que tengo * fij * ("find in java") y * fix * (find in xml) y * fix * (find in text files) etc. alias pero ...Tal * find * no está haciendo lo mismo que un IDE: también le mostrará comentarios usando esa cadena y métodos de otros paquetes que tengan el mismo nombre, que es un PITA principal. Así que, por mucho que lo use de vez en cuando, no es tan bueno como lo que hace un IDE. – SyntaxT3rr0r

0

Lo más cercano que pude encontrar fue el método descrito en esta respuesta seleccionada StackOverflow questions. check this out

-1

Puede hacer esto con algo en su IDE como "Buscar Usos" (que es lo que se llama en Netbeans y JDeveloper). Un par de cosas a tener en cuenta:

  1. Si su método implementa un método de una clase de interfaz o base, sólo se puede saber que su método se llama POSIBLEMENTE.
  2. Muchos frameworks de Java usan Reflection para llamar a su método (IE Spring, Hibernate, JSF, etc.), así que tenga cuidado con eso.
  3. En la misma nota, su método podría ser llamado por algún marco, reflexivo o no, así que de nuevo tenga cuidado.
2

No hay una forma de hacerlo (mediante programación) a través de las bibliotecas de reflexión de Java. No puede solicitar un java.lang.reflect.Method "¿a qué métodos llama?"

que deja a otras dos opciones que se me ocurren:

  1. El análisis estático del código fuente. Estoy seguro de que esto es lo que hace el conjunto de herramientas de Eclipse Java: puede ver el origen de Eclipse detrás del JDT y encontrar qué hace cuando le pide a Eclipse que "Encuentre referencias" para un método.

  2. Análisis de código de bytes. Puede inspeccionar el bytecode para las llamadas al método. No estoy seguro de qué bibliotecas o ejemplos hay disponibles para ayudar con esto, pero no puedo imaginar que algo no exista.

4

Anotar el método con @Deprecated (o etiquetarlo con @deprecated), encienda las advertencias de obsolescencia, ejecutar su compilación y ver qué advertencias disparará.

La ejecutar su compilación poco se puede hacer ya sea mediante la invocación de un proceso de hormigas externo o mediante el uso de la Java 6 compiler API.

3

en Eclipse, resalte el nombre del método y luego Ctrl + Shift + G

5
  1. haga clic derecho en método
  2. Ir a las referencias una d (según sus necesidades)
    elija el espacio de trabajo/proyecto/Jerarquía.

Aparece un panel que muestra todas las referencias a estas funciones. Eclipse FTW!

0

Hice un pequeño ejemplo usando el de @ Chadwick. Es una prueba que evalúa si las llamadas a getDatabaseEngine() se realizan mediante métodos que implementan @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) en Eclipse es -> clic derecho sobre el método y seleccione jerarquía de llamadas abierto o CLT+ALT+H

2) En jdeveloper es -> clic derecho sobre el método y seleccionar las llamadas o ALT+SHIFT+H

Cuestiones relacionadas