Как получить исходный файл-имя / номер строки из java.ленг.Объект класса

возможно ли, учитывая java.lang.Class object, чтобы получить имя исходного файла и номер строки, в которой был объявлен класс?

данные должны быть доступны в .class информация об отладке файла. Единственное место, о котором я знаю, где JDK возвращает такую отладочную информацию, находится в java.lang.StackTraceElement но я не уверен, что можно заставить Java создать java.lang.StackTraceElement экземпляр для произвольного класса, потому что мы не выполняем метод в классе.

мой точный случай использования-это анонимный внутренний класс, который имеет имя, сгенерированное компилятором. Я хочу знать имя файла и номер строки для объявления класса.

Я предпочитаю не использовать фреймворк манипуляции байтовым кодом, но я могу вернуться к нему, если придется.

7 ответов


ответ на это сводится к тому, насколько вы контролируете код, который реализует прослушиватель. Вы правы, что невозможно создать stacktrace, не будучи в методе.

общий метод заключается в создании исключения () в конструкторе, но не бросайте его. Это содержит информацию stacktrace, которую вы можете использовать, как хотите. Это даст вам номер строки конструктора, но не класса. Обратите внимание, что этот метод не особенно эффективен, потому что создание stacktrace дорого.

вам потребуется:

  1. принудительно выполнить некоторый код в конструкторе (относительно легко, если ваш слушатель является абстрактным классом, которым вы управляете)
  2. инструмент код каким-то образом (лечение кажется хуже, чем болезнь здесь).
  3. сделайте некоторые предположения о том, как называются классы.
  4. прочитайте банку (сделайте то же самое, что и javac - p)

для 1), вы просто поместите создание исключения в абстрактный класс, и конструктор вызывается подклассом:

class Top {
    Top() {
        new Exception().printStackTrace(System.out);
    }
}

class Bottom extends Top {
    public static void main(String[] args) {
        new Bottom();
    }
}

это производит что-то вроде:

java.lang.Exception
    at uk.co.farwell.stackoverflow.Top.<init>(Top.java:4)
    at uk.co.farwell.stackoverflow.Bottom.<init>(Bottom.java: 11)
    at uk.co.farwell.stackoverflow.Bottom.main(Bottom.java: 18)

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

возможно, как указал jmg, вы можете определить несколько классов верхнего уровня в одном файле. Если у вас есть открытый класс Foo, это должно быть определено в Foo.java, но непубличный класс может быть включен в другой файл. Вышеуказанный метод справится с этим.

пояснение:

если вы разберете java (javap-c-verbose), вы увидите, что в отладочной информации есть номера строк, но они применяются только к методам. Используя следующий внутренний класс:

static class Consumer implements Runnable {
    public void run() {
        // stuff
    }
}

и выход javap содержит:

uk.co.farwell.stackoverflow.Actors$Consumer();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #10; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable: 
   line 20: 0

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      5      0    this       Luk/co/farwell/stackoverflow/Actors$Consumer;

LineNumberTable содержит список номеров строк, которые применяются к методу. Итак, мой конструктор для потребителя начинается с строки 20. Но это первый строка конструктора, не первая строка класса. Это только одна и та же строка, потому что я использую конструктор по умолчанию. Если я добавлю конструктор, то номера строк изменятся. компилятор не хранит строку, в которой объявлен класс. Таким образом, вы не можете найти, где объявлен класс без разбора самой java. У вас просто нет доступной информации.

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

Runnable run = new Runnable() {
    public void run() {
    }
};

затем номер строки конструктора и класса будет соответствовать [*], поэтому это дает вам номер строки.

[*] за исключением случаев, когда "new" и "Runnable()" находятся в разных строках.


вы можете найти текущую строку кода uut:

Throwable t = new Throwable();
System.out.println(t.getStackTrace()[0].getLineNumber());

но это выглядит как StackTraceElements создаются собственным методом JDK внутри Throwable.

public synchronized native Throwable fillInStackTrace();

событие если вы используете фреймворк манипуляции байтовым кодом, чтобы добавить метод в класс, который создает throwable, вы не получите правильную строку кода объявления класса.


вы можете получить трассировку стека из любого потока путем вызова getStackTrace(). Поэтому для текущего потока вы должны вызвать Thread.currentThread().getStackTrace().


для ваших целей создание исключения только для трассировки стека является правильным ответом.

но в случаях, когда это не работает, вы также можете использовать Апач как bcel для анализа байт-код Java. Если это звучит как жестокое убийство, вы, вероятно, правы.

public boolean isScala(Class jvmClass) {
   JavaClass bpelClass = Repository.lookupClass(jvmClass.getName());
   return (bpelClass.getFileName().endsWith(".scala");
}

(предупреждение: я не тестировал этот код.)

другой вариант-создать пользовательский Doclet для сбора соответствующих метаданных во время компиляции. Я использовал это в прошлом для приложения, которое должно было знать во время выполнения все подклассы определенного суперкласса. Добавление их к заводскому методу было слишком громоздким, и это также позволило мне напрямую связаться с javadoc.

теперь, когда я пишу новый код в Scala, я рассматриваю возможность использования метода, подобного приведенному выше, и создания списка классов путем поиска в каталоге сборки.


вот как я это сделал:

import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * This class is used to determine where an object is instantiated from by extending it. 
 */
public abstract class StackTracer {

    protected StackTracer() {
        System.out.println( shortenedStackTrace( new Exception(), 6 ) );
    }

    public static String shortenedStackTrace(Exception e, int maxLines) {
        StringWriter writer = new StringWriter();
        e.printStackTrace( new PrintWriter(writer) );
        String[] lines = writer.toString().split("\n");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < Math.min(lines.length, maxLines); i++) {
            sb.append(lines[i]).append("\n");
        }
        return sb.toString();
    }

}

EDIT: оглядываясь назад, я бы, вероятно, использовал AOP для этого сейчас. На самом деле, некоторые незначительные изменения в этот проект и я, вероятно, смогу это сделать. вот вопрос стека с намеком.


возможно ли, учитывая java.ленг.Экземпляр класса, чтобы получить имя исходного файла и номер строки, в которой был объявлен класс?

исходный файл в большинстве случаев тесно связаны с именем класса. В этом файле есть только одна строка, в которой объявлен класс. Нет необходимости в кодировании такого рода однозначной информации в отладочной информации.

данные должны быть доступны в .файл класса debug info

почему?


этот ответ не охватывает номера строк, но он отвечает моим потребностям и должен работать для любого исходного файла, который у вас есть.

это ответ на поиск местоположения исходного файла на основе файла класса для среды IDE с несколькими проектами. Он использует загрузчик классов и соглашения проекта java для решения этой проблемы.

вам нужно будет обновить исходную папку (src) и выходную папку (bin) в соответствии с вашим соглашением. Вы должны будете предоставить свой собственный реализация

String searchReplace(old, new, inside)

здесь код

public static String sourceFileFromClass(Class<?> clazz) {
    String answer = null;
    try {
        if (clazz.getEnclosingClass() != null) {
            clazz = clazz.getEnclosingClass();
        }
        String simpleName = clazz.getSimpleName();

        // the .class file should exist from where it was loaded!
        URL url = clazz.getResource(simpleName + ".class");
        String sourceFile = searchReplace(".class", ".java", url.toString());
        sourceFile = searchReplace("file:/", "", sourceFile);
        sourceFile = searchReplace("/bin/", "/src/", sourceFile);
        if( new java.io.File(sourceFile).exists() ) {
            answer = sourceFile;
        }
    }
    catch (Exception ex) {
        // ignore
    }

    return answer;
}