Всем привет! На связи команда Explyt Spring. Недавно была статья от JetBrains, а также ее перевод на Habr, где они рассказывали о новом плагине Spring Debugger и о том как используют нативный код Spring для лучшего понимания контекста приложения. С помощью non-suspending breakpoints JetBrains «вклиниваются» в жизненный цикл Spring и собирают все необходимые для плагина данные. Но ведь это почти тоже что и мы пытались делать в нашем плагине с помощью javaagent, о котором писали статьи. И нам даже показалось, что они вдохновлялись нашими статьями, первая из которых увидела свет в октябре 2024 года (возможно только показалось).
Нужно отдать должное тому как они интегрировали эту идею в их плагин — данные о контексте сами подгружаются в процессе дебага, максимально прозрачно для пользователя и без лишних действий с его стороны. У нас же, требовалось загружать контекст приложения вручную, выбрав соответствующую «ран» конфигурацию, на основе которой мы получали данные о приложении, что стало предметом конструктивной критики со стороны пользователей.
Нам эта идея понравилась, к тому же у нас почти все готово - есть javaagent с помощью которого мы патчим байт код Spring и получаем его контекст, поэтому возьмем текущий наш механизм и просто улучшим UX.
Что получилось
Чтобы иметь постоянный доступ к Spring context нам нужно где то сохранить ссылку на него. Для этого доработаем уже имеющийся javaagent. С помощью нашей библиотеки добавим класс который будет хранить ссылку на контекст.
@AddClass
public class Explyt {
public static AbstractApplicationContext context;
public static AbstractApplicationContext getContext() {
return context;
}
public static ConfigurableEnvironment getEnvironment() {
return context.getEnvironment();
}
public static ConfigurableListableBeanFactory getBeanFactory() {
return context.getBeanFactory();
}
}
И далее в процессе инструментирования метода registerBeanPostProcessors, как мы делали в прошлой статье, сохраним туда ссылку на контекст.
@Decorate
@OriginClass("org.springframework.context.support.AbstractApplicationContext")
public class AbstractApplicationContextDecorator {
@DecoratedMethod
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
__registerBeanPostProcessors(beanFactory);
setExplytContextForDebug(debugRunConfigurationId);
...
}
@AddMethod
private void setExplytContextForDebug(String debugRunConfigurationId) {
try {
Class<?> aClassContext = Class.forName("explyt.Explyt");
Field contextField = aClassContext.getDeclaredField("context");
contextField.set(null, this);
System.out.println("Explyt Spring Debug attached");
} catch (Exception e) {
throw new RuntimeException("no context class", e);
}
}
}
В процессе дабага мы добавляем наш javaagent к запускаемой Run Configuration и собираем необходимые нам данные. Вот собственно и все. Осталось дело за малым - UI.
Spring Debugger
Во первых мы доработали окно отладчика, вкладку Theads&Variables.
Здесь сразу видна информация о контексте приложения — Explyt: Spring Context и о текущей активной транзакции, еслиона есть — Explyt: Active Transaction, которая помимо краткой инфы о параметрах транзакции содержит ссылку на объект TransactionStatus (данные о транзакции на данный момент работает только для JDBC и все что работает поверх него).
В Explyt Spring тул окне, появляется информация о текущей сессии отладки — DebugSession, которая, как и раньше, отображает данные о «бинах» проекта в древовидной структуре.


Также в Evaluate Expression мы можем сразу получить доступ к контексту — через специальную переменную Explyt.context, благодаря тому что мы инструментировали байт код приложения и добавили туда свой класс, сохранив в нем ссылку на контекст (о чем рассказали в самом начале). Поэтому можно извлечь оттуда любой «бин» и вызвать на нем любой метод, даже если ссылки на него нет в текущей точке останова. Например можно отправить сообщение в Kafka или вызвать метод любого «бина»:

Были добавлены полезные утилиты: Explyt.getBeanFactory(), Explyt.getEnvironment() — теперь разработчики могут быстро получать информацию о «бинах» и текущий Environment с которым запущено приложение — настройки, проперти, активные профили и др.

Кроме того, для любого наследника Repository из Spring Data добавили «лайн»‑маркеры для методов, которые позволяют выполнить их сразу, при условии что находимся на «паузе» в дебаге. При выборе этого действия мы автоматически откроем диалоговое окно Evaluate Expression и вставим туда необходимый фрагмент кода. Вам нужно будет заполнить необходимые аргументы метода, если они нужны и выполнить код.

В процессе отладки «бины», которые не попадают в контекст, отображаются с бледным лайн маркером на уровне класса:

Также наш «отладчик» работает со всеми приложениями где есть «спринговый» контекст, не обязательно Spring Boot. Это могут быть как и обычные Spring приложения, так и Spring тесты:

Заключение
В итоге мы пришли к следующему workflow:
в режиме дебага, полагаемся на те данные что пришли из Spring и имеем максимально точный контекст приложения.
в обычном режиме без дебага мы строим примерный контекст на основе исходников. Примерно также работает и Ultimate Spring plugin. Точный контекст по исходникам построить невозможно — об этом мы подробно рассказали тут.
-
старый функционал, когда пользователи могли загружать инфо о Spring контексте на основе «ран»‑конфигураций («иконка» Spring Boot с лупой напротив «ран»‑конфигурации), мы также оставили. Но если вы хотите использовать его как и раньше для более точной DI подсветки/навигации, отображения Spring аспектов и прочего в процессе работы с кодом (без дебага), то мы для этого добавили переключатель в Explyt Spring Tool Window — «Use for DI». Двойной клик по которому активирует данный контекст в нашем плагине.
При необходимости, можно отключить данный функционал в настройках (File | Settings | Tools | Explyt Spring | Enable Spring debug mode).
Скачать последнюю версию плагина со Spring Debugger можно следуя инструкции с автоматическими обновлениями или напрямую с GitHub Releases, но без обновлений.
Spring Debugger доступен в полном объеме в версиях IDEA начиная с 243. В 241/242 мы бэкпортили его в урезанном виде т. к. в старых версиях IDEA отсутствуют нужные нам точки расширения. Но там вы по прежнеме можете использовать наши переменные Explyt.context, Explyt.getBeanFactory(), Explyt.getEnvironment() и производить любые манипуляции с контекстом как было описано выше в диалоговом окне — Evaluation Expression.
Приглашаем вас попробовать наш плагин, а также делиться своими отзывами и предложениями. Ваша обратная связь поможет нам сделать инструмент более полезным и удобным. Также напоминаем, что плагин имеет открытый исходный код, который доступен на GitHub.
Будем благодарны за ваши вопросы и идеи: GitHub Issues, Telegram-чат, а также личные сообщения в нашем профиле на Habr.