Новое событие в JFR для диагностики использования устаревшего (deprecated) кода
В Java есть специальная аннотация @Deprecated для маркировки уставшего кода. С определенной периодичностью такой код из JDK удаляется. Обычно о конкретных сроках удаления анонс делается заранее и в теории можно успеть подготовиться, но на практике не все так просто.
В больших проектах найти куски устаревшего кода в куче зависимостей задача не тривиальная и требующая хорошей автоматизации. В этой ситуации к нам приходит на помощь новый тип события в JFR. Он был добавлен в JDK 22.
Давайте посмотрим на простом примере как это работает.
Для начала создадим простой класс и в его методе main вызовим любой @Deprecated метод. Для примера я выбрал конструктор класса java.net.URL(String). Со списком всех методов помеченных аннотацией @Deprecated вы можете ознакомиться по этой ссылке
public class App { public static void main(String[] args) throws MalformedURLException { URL url = new URL("https://habr.com/ru/"); } }
Если вы попробуете скомпилировать его при помощи javac, то получите собщение c предупреждением о том, что у вас в коде используется устаревший код.
~/IdeaProjects/Tests/src/main/java (master*) » javac App.java user@user-home Note: App.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details.
Если добавить в параметры запуска еще и -Xlint:deprecation вывод станет чуть более информативен.
~/IdeaProjects/Tests/src/main/java (master*) » javac -Xlint:deprecation App.java user@user-home App.java:8: warning: [deprecation] URL(String) in URL has been deprecated URL url = new URL("https://habr.com/ru/"); ^ 1 warning
Но таким "дедовским" способом реальные проекты никто не собирает. Обычно используются инструменты автоматизирующие сборку. На данный момент довольно широко используются Maven и Gradle.
Если сделать простой Maven проект с таким pom.xml, то к удивлению мы не получим warning при компиляции того файла.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.github.rodindenis</groupId> <artifactId>Tests</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>22</maven.compiler.source> <maven.compiler.target>22</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project>
Для получегия таких warning нужно будет еще добавить параметр конфигурации showDeprecation для maven-compiler-plugin.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.github.rodindenis</groupId> <artifactId>Tests</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>22</maven.compiler.source> <maven.compiler.target>22</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <showDeprecation>true</showDeprecation> </configuration> </plugin> </plugins> </build> </project>
Что будет если этот кусок кода уедет в библиотеку и мы с вами будем ее получать как уже скомпилированную зависимость? Ответ под спойлером.
Так как компилятор проверяет в момент компиляции, то соответственно warning в момент компиляции нашего кода мы не получим.
Давайте проверим? Я вынес вызов устаревшего кода в отдельный сабмодуль.
package com.github.rodindenis.jfr.event.lib; import java.net.MalformedURLException; import java.net.URL; public class Printer { public static void print(String url) throws MalformedURLException { System.out.println(new URL(url).toString()); } }> </build> </project>
package com.github.rodindenis.jfr.event.main; import com.github.rodindenis.jfr.event.lib.Printer; import java.net.MalformedURLException; public class App { public static void main(String[] args) throws MalformedURLException { Printer.print("https://habr.com/ru/"); } }
Примеры pom.xml здесь не привожу. Их можно будет посмотреть в репозитории.
Как и ожидалось после компиляции класса Printer мы получаем warning, но вот когда мы уже подключаем уже скомпилированную библиотеку с этим классом как зависимость и пробуем скомпилировать App, то warning мы уже не ловим.
Для воспроизведения ситуации в скаченном из репозитория кода проведите следующие манипуляции. Сначала скомпилируйте и установитие все артефакты. На этом этапе вы увидите в логе warning при компиляции библиотеки.
mvn clean install
Так как у нас теперь локально установлена скомпилированная библиотека с кодом класса Printer, то мы можем попробовать отдельно перекомпилировать только основной код App. Эту команду выполняем только для сабмодуля main с классом App.
mvn clean package
И вот пришло время ощутить всю силу JFR.
Запускаем наше приложение с включенным JFR логированием в файл. Я перешел в каталог main/target своего проекта и из него выполнил команду
~/.jdks/temurin-22.0.2/bin/java -XX:StartFlightRecording:jdk.DeprecatedInvocation#level=all,filename=recording.jfr -cp ../../lib/target/lib-1.0-SNAPSHOT.jar:./main-1.0-SNAPSHOT.jar com.github.rodindenis.jfr.event.main.App
В текущем каталоге появился файл recording.jfr. Давайте посмотрим есть ли в нем нужное нам событие.
~/.jdks/temurin-22.0.2/bin/jfr print --events jdk.DeprecatedInvocation recording.jfr
jdk.DeprecatedInvocation { startTime = 16:39:18.769 (2024-08-19) method = java.net.URL.<init>(String) invocationTime = 16:39:18.759 (2024-08-19) forRemoval = false stackTrace = [ com.github.rodindenis.jfr.event.lib.Printer.print(String) line: 9 ... ] }
Важная оговорка. Данный подход позволит вам в рантайме выявить вызовы устаревшего кода, но если код не вызывается, то и события такого вы не получите.