1. Обзор
В этой короткой статье мы рассмотрим, почему мы можем видеть неизвестный источник в нашей трассировке стека исключений Java и как мы можем это исправить.
2. Информация об отладке класса
Файл класса Java содержит необязательную отладочную информацию для облегчения отладки. Во время компиляции мы можем выбрать, будет ли и какая вся отладочная информация добавляться в файлы классов. Это определит, какая отладочная информация будет доступна во время выполнения.
Давайте изучим справочную документацию компилятора Java, чтобы увидеть различные доступные параметры:
javac -help
Usage: javac <options> <source files>
where possible options include:
-g Generate all debugging info
-g:none Generate no debugging info
-g:{lines,vars,source} Generate only some debugging info
По умолчанию компилятор Java добавляет строки и исходную информацию в файлы классов, что эквивалентно -g:lines,source.
2.1. Компиляция с опцией отладки
Давайте посмотрим, что произойдет, когда мы скомпилируем наши классы Java с указанными выше параметрами. У нас есть класс Main
, который намеренно генерирует исключение StringIndexOutOfBoundsException
.
В зависимости от используемого механизма компиляции нам нужно будет соответствующим образом указать параметр компиляции. Здесь мы будем использовать Maven и его плагин компилятора для настройки параметров компилятора:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<compilerArgs>
<arg>-g:none</arg>
</compilerArgs>
</configuration>
</plugin>
Мы установили для параметра -g
значение none
, что означает, что для наших скомпилированных классов не будет генерироваться отладочная информация. Запуск нашего ошибочного класса Main
генерирует трассировку стека, в которой мы видим неизвестные источники вместо номера строки, в которой произошло исключение.
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: begin 0, end 10, length 5
at java.base/java.lang.String.checkBoundsBeginEnd(String.java:3751)
at java.base/java.lang.String.substring(String.java:1907)
at com.foreach.unknownsourcestacktrace.Main.getShortenedName(Unknown Source)
at com.foreach.unknownsourcestacktrace.Main.getGreetingMessage(Unknown Source)
at com.foreach.unknownsourcestacktrace.Main.main(Unknown Source)
Давайте посмотрим, что содержит сгенерированный файл класса. Мы будем использовать javap
, который является дизассемблером файлов классов Java, чтобы сделать это:
javap -l -p Main.class
public class com.foreach.unknownsourcestacktrace.Main {
private static final org.slf4j.Logger logger;
private static final int SHORT_NAME_LIMIT;
public com.foreach.unknownsourcestacktrace.Main();
public static void main(java.lang.String[]);
private static java.lang.String getGreetingMessage(java.lang.String);
private static java.lang.String getShortenedName(java.lang.String);
static {};
}
Может быть трудно понять, какую отладочную информацию мы должны здесь ожидать, поэтому давайте изменим параметр компиляции и посмотрим, что произойдет.
2.3. Исправление
Давайте теперь изменим параметр компиляции на -g:lines,vars,source,
который поместит информацию LineNumberTable ,
LocalVariableTable
и Source
в наши файлы классов. Это также эквивалентно простому наличию -g
, который помещает всю отладочную информацию:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<compilerArgs>
<arg>-g</arg>
</compilerArgs>
</configuration>
</plugin>
Запуск нашего основного
класса с ошибками теперь производит:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: begin 0, end 10, length 5
at java.base/java.lang.String.checkBoundsBeginEnd(String.java:3751)
at java.base/java.lang.String.substring(String.java:1907)
at com.foreach.unknownsourcestacktrace.Main.getShortenedName(Main.java:23)
at com.foreach.unknownsourcestacktrace.Main.getGreetingMessage(Main.java:19)
at com.foreach.unknownsourcestacktrace.Main.main(Main.java:15)
Вуаля, мы видим информацию о номере строки в нашей трассировке стека. Посмотрим, что изменилось в нашем файле класса:
javap -l -p Main
Compiled from "Main.java"
public class com.foreach.unknownsourcestacktrace.Main {
private static final org.slf4j.Logger logger;
private static final int SHORT_NAME_LIMIT;
public com.foreach.unknownsourcestacktrace.Main();
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/foreach/unknownsourcestacktrace/Main;
public static void main(java.lang.String[]);
LineNumberTable:
line 12: 0
line 13: 8
line 15: 14
line 16: 29
LocalVariableTable:
Start Length Slot Name Signature
0 30 0 args [Ljava/lang/String;
8 22 1 user Lcom/foreach/unknownsourcestacktrace/dto/User;
private static java.lang.String getGreetingMessage(java.lang.String);
LineNumberTable:
line 19: 0
LocalVariableTable:
Start Length Slot Name Signature
0 28 0 name Ljava/lang/String;
private static java.lang.String getShortenedName(java.lang.String);
LineNumberTable:
line 23: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 name Ljava/lang/String;
static {};
LineNumberTable:
line 8: 0
}
Наш файл класса теперь содержит три важные части информации:
- Source , верхний заголовок, указывающий на файл
.java
, из которого был создан файл.class
. В контексте трассировки стека он предоставляет имя класса, в котором произошло исключение. - LineNumberTable сопоставляет номер строки в коде, который на самом деле запускает JVM, с номером строки в нашем файле исходного кода. В контексте трассировки стека он предоставляет номер строки, в которой произошло исключение. Нам также нужно это, чтобы иметь возможность использовать точки останова в наших отладчиках.
- LocalVariableTable содержит сведения для получения значения локальной переменной. Отладчики могут использовать его для чтения значения локальной переменной. В контексте трассировки стека это не имеет значения.
3. Заключение
Теперь мы знакомы с отладочной информацией, генерируемой компилятором Java. Способ манипулирования ими, опция компилятора -g .
Мы увидели, как это можно сделать с помощью подключаемого модуля компилятора Maven.
Итак, если мы находим неизвестные источники в наших трассировках стека, мы можем исследовать наши файлы классов, чтобы проверить, доступна ли отладочная информация или нет. После чего мы можем выбрать правильный вариант компиляции на основе нашего инструмента сборки для решения этой проблемы.
Как всегда, полный код и конфигурации Maven доступны на GitHub .