Всем привет! На связи команда 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, которая, как и раньше, отображает данные о «бинах» проекта в древовидной структуре.

Отладка с Explyt Spring Debugger
Отладка с Explyt Spring Debugger
Explyt: Spring Context и Explyt: Active Transaction
Explyt: Spring Context и Explyt: Active Transaction

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

Вычисление выражений с помощью утилитного класса Explyt
Вычисление выражений с помощью утилитного класса Explyt

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

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

Spring Data Repository
Spring Data Repository

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

"бин" SimpleService не в контексте
"бин" SimpleService не в контексте

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

Запуск 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.

Комментарии (0)