Особенности сборки native приложений в Quarkus через GraalVM

Особенности сборки native приложений в Quarkus через GraalVM

Столкнулся с некоторыми проблемами:


Ошибки типа Discovered unresolved type during parsing… и Discovered unresolved method during parsing

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

quarkus.native.additional-build-args=--initialize-at-run-time.....

но это оказалось бесполезно, т.к. при сборке, грааль сканирует весь код и находит места вызовов несуществующих классов/методов, и если найдёт, то прекращает сборку и вываливает ошибку.

На эту тему есть небольшое замечание тут.

Как скипнуть эту проблему или игнорировать такие вещи — не нашёл. Разработчики пишут, что они заботятся о нас, чтобы наша приложуха не дай бог не выкинула ошибку типа ClassNotFoundException.

Причина появления этой ошибки — Внутри одной из зависимостей/транзитивных зависимостей есть код, который:

  • импортирует классы из неподключенных зависимостей
  • использует версию зависимости, в которой нет вызываемого класса/метода

При сборке приложения в jar проблем не возникает, т.к. сборщик не копает внутрь работы зависимостей и не смотрит как там взаимодействуют между собой транзитивные зависимости, а просто собирает проект и всё.

При запуске приложения из jar, классы инициализируются при обращении к ним, и если бы было обращение, то вылетела бы ошибка типа ClassNotFoundException/MethodNotFoundException или тому подобные.

Конечно, следует добиваться максимальной совместимости всех используемых зависимостей, но не редки случаи, когда это сделать по каким-то причинам невозможно. Также велика вероятность несовместимости каких-то глубоких транзитивных зависимостей, код которых даже никогда не будет использоваться вашей бизнес логикой. Для исправления такой ситуации я нашёл единственное костыльное решение:

— написать свою реализацию того класса из внешней библиотеки, которая дёргает несуществующий метод, положить его в какую-то отдельную зависимость и подключить её до подключения проблемной зависимости, либо оставить лежать прям в проекте по адресу оригинального пакета. Тогда класслоадер увидит что такой класс уже есть, и не станет грузить оригинальный., и наверно проблемы не будет


При обмене сообщениями через JMS со сторонними сервисами, возникает ошибка десериализации содержимого сообщений, если в сообщениях передаются java-объекты сериализованные java-сериализацией. Причём ему абсолютно безразлично, что на объекты ставишь статический serialVersionUID, при сериализации он каким-то чудесным образом его игнорирует и вычисляет свой. Да, здесь есть проблема, что значение данного поля извлекается при помощи рефлексии, которая тоже по-умолчанию не работает и её необходимо разрешать, но всё-равно перезапись значения serialVersionUID происходит независимо от этого.

Единственным решением проблемы явился переход с java-сериализацию на json-сериализацию.


Разрешение рефлексии.

В основном, проблема возникает при попытке сериализовать/десериализовать классы в json при помощи jackson. Для его работы нужно разрешить использование рейфлексии для классов, которые предполагается использовать для отображения json в объекты. В простом приложении, в достаточно установить над ними аннотацию @RegisterForReflection и всё заработает, но что делать в более тяжелых случаях?

  • @RegisterForReflection работает, но только в коде самого приложения. Если класс подключен в виде зависимости, то аннотация не работает. В таком случае поможет создание в коде проекта любого пустого класса с аннотацией @RegisterForReflection, в которой в параметре targets следует перечислить все классы из внешних пакетов, которые следует зарегистрировать для рефлексии. Да, класс может быть абсолютно пустой с единственной аннотацией @RegisterForReflection, кваркус его нормально подхватит. Нет, пакеты передавать в targets не получится, только список конкретных классов. Для этой цели есть вариант использовать @BuildStep, но у меня не получилось.
  • В @RegisterForReflection.targets можно передавать интерфейсы, и они будут подхватывать все имплементации этих интерфейсов, но только при условии что и интерфейс и его реализации находятся в коде приложения. Т.е. если у вас что-то из них приходит из внешней зависимости — работать подключение через интерфейс не будет.
  • Для динамически создаваемых классов, типа @ProxyGen, есть единственный вариант — использовать объявление через reflection-config.json и передавать его через аргумент сборки quarkus.native.additional-build-args = —H:ReflectionConfigurationFiles=reflection-config.json. При этом сам файл нужно размещать в корне папки resources.
(Просмотрено 95 раз, 1 раз за сегодня)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *