Как я могу найти все методы, которые вызывают данный метод в Java?

Мне нужно получить список всех методов вызова для интересующего меня метода в Java. Есть ли инструмент, который может помочь мне с этим?

Изменить: я забыл упомянуть, что мне нужно сделать это из программы. Я использую Java Pathfinder, и я хочу запустить его всеми методами, которые вызывают мой интересующий метод.

11 ответов

Решение

Для анализа байт-кода я бы рекомендовал ASM. Имея список классов для анализа, можно создать посетителя, который найдет интересующие вас вызовы методов. Ниже приведена одна реализация, которая анализирует классы в jar-файле.

Обратите внимание, что ASM использует внутренние имена с '/' вместо '.' в качестве разделителя. Укажите целевой метод в качестве стандартного объявления без модификаторов.

Например, чтобы перечислить методы, которые могут вызывать System.out.println("foo") в 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)"

Редактирование: добавлены номера источника и строки: Обратите внимание, что это указывает только на последний вызов целевого метода для каждого вызывающего метода - исходный 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();
        }
    }

}

Изменить: исходный вопрос был отредактирован, чтобы указать, что необходимо решение во время выполнения - этот ответ был дан перед этим редактированием и указывает только, как это сделать во время разработки.

Если вы используете Eclipse, вы можете щелкнуть правой кнопкой мыши по методу и выбрать "Открыть иерархию вызовов", чтобы получить эту информацию.

Обновляется после прочтения комментариев: другие IDE поддерживают это также аналогичным образом (по крайней мере, Netbeans и IntelliJ делают)

  1. щелкните правой кнопкой мыши по методу
  2. Перейти к ссылкам и (в зависимости от ваших требований)
    выберите рабочее пространство / проект / иерархию.

Это открывает панель, которая показывает все ссылки на эти функции. Затмение FTW!

Аннотируйте метод с помощью @Deprecated (или пометьте его с помощью @deprecated), включите предупреждения об устаревании, запустите компиляцию и посмотрите, какие предупреждения сработали.

Выполнить ваш бит компиляции можно либо с помощью вызова внешнего процесса ant, либо с помощью API компилятора Java 6.

В затмении выделите имя метода, а затем Ctrl+Shift+G

Нет способа сделать это (программно) с помощью библиотек отражения Java - вы не можете спросить java.lang.reflect.Method "какие методы вы вызываете?"

Это оставляет два других варианта, которые я могу придумать:

  1. Статический анализ исходного кода. Я уверен, что это именно то, что делает набор инструментов Eclipse Java - вы можете посмотреть на источник Eclipse, стоящий за JDT, и выяснить, что он делает, когда попросите Eclipse "Найти ссылки" на метод.

  2. Анализ байт-кода. Вы можете проверить байт-код для вызовов метода. Я не уверен, какие библиотеки или примеры существуют, чтобы помочь с этим - но я не могу вообразить, что что-то не существует.

Я сделал небольшой пример, используя пример @ Чедвика. Это тест, который оценивает, выполняются ли вызовы 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();
}

Да, большинство современных сред IDE позволяют вам искать использование метода или переменной. В качестве альтернативы вы можете использовать отладчик и установить точку трассировки в записи метода, распечатывая трассировку стека или что-либо еще при каждом вызове метода. Наконец, вы можете использовать несколько простых утилит оболочки, чтобы просто использовать grep для метода, например:

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

Единственный метод, который позволит вам найти вызовы, сделанные с помощью некоторого метода отражения, - это использование отладчика.

1) В Eclipse это -> щелкните правой кнопкой мыши на методе и выберите открытую иерархию вызовов или CLT+ALT+H

2) В jdeveloper это -> щелкните правой кнопкой мыши на метод и выберите вызовы или ALT+SHIFT+H

Самым близким, что я смог найти, был метод, описанный в этом ответе на вопросы Stackru. Проверь это

Вы можете сделать это с помощью чего-то в своей среде IDE, например "Найти использование" (как это называется в Netbeans и JDeveloper). Несколько вещей, чтобы отметить:

  1. Если ваш метод реализует метод из интерфейса или базового класса, вы можете знать только, что ваш метод ВОЗМОЖНО вызван.
  2. Многие платформы Java используют Reflection для вызова вашего метода (IE Spring, Hibernate, JSF и т. Д.), Поэтому будьте осторожны с этим.
  3. В то же время, ваш метод может вызываться каким-то фреймворком, рефлексивно или нет, поэтому снова будьте осторожны.
Другие вопросы по тегам