Подтвердить что ты не робот

Заменить содержимое некоторых методов во время выполнения

Я хотел бы заменить содержимое некоторых методов во время выполнения.

Я знаю, что я могу использовать javassist для этого, но он не работает, потому что классы, которые я хотел бы улучшить, уже загружаются системным classLoader.

Как я могу это сделать, чтобы заменить содержимое метода во время выполнения? Должен ли я попытаться выгрузить класс? Как я могу это сделать? Я видел, что это возможно, но я не мог понять, как это сделать.

Если возможно, я хотел бы избежать использования внешней библиотеки для этого, я хотел бы закодировать его сам.

Дополнительная информация: - Класс, который я хотел бы улучшить, содержится в структуре (в файле jar) - Мой код на самом деле является плагином этой структуры - Структура, в которой работает мой плагин, имеет свой собственный классLoader, но этот классLoader не загружает свои собственные классы (он делегирует их загрузчику системного класса) - Структура, которую я использую, - это Play.

Благодарим за помощь!

4b9b3361

Ответ 1

Вы можете сделать это с помощью Javaassist, а также с любой другой технической библиотекой байт-кода. Магия заключается в API Java Attach API, который позволяет программам подключаться к запущенным JVM (и модифицировать загруженные классы).

Его можно найти в пакете com.sun.tools.attach, и, как следует из названия, специфично для JVM Oracle. Тем не менее, инструменты JDK, такие как jstack и jmap, используют его для поддержки своей функции "прикреплять к запущенной JVM", поэтому можно с уверенностью сказать, что это здесь.

Документы API-интерфейса Attach довольно описательны, и этот сообщение в блоге Oracle демонстрирует прикрепление агента во время выполнения. В общем, это сводится к:

  • Сделайте программу ретрансляции "обычным" способом -javaagent, с premain et al
  • Переименуйте premain в agentmain
  • Создайте временный файл JAR, содержащий ваши классы агентов, и указав манифест Agent-Class на ваш агент (agentmain -содержащий) класс, а Can-Retransform-Classes - на true
  • Получить PID целевой JVM (возможно, тот же процесс) и прикрепить к нему временную банку

К счастью, API может сделать это без особых усилий с вашей стороны, хотя, если вы выполняете генерацию JAR во время выполнения, может быть немного сложно упаковать все классы, необходимые вашему агенту.

Я надеялся включить демонстрационный агент, демонстрирующий прикрепление профилировщика во время выполнения, но он оказался слишком длинным для публикации. Тем не менее, я поставил его в Github repo.

Предостережение этого подхода состоит в том, что он заставляет вашу программу зависить от tools.jar, которая поставляется с JDK, и которая отсутствует в JRE. Вы можете обойти это, отправив tools.jar с (или извлеченным) в ваше приложение, но вам все равно нужно предоставить родную библиотеку attach, необходимую API-интерфейсу Attach для вашего приложения. Я включил библиотеки для всех платформ, которые я мог найти в репозитории, описанном выше, хотя вы также можете получить их сами.

В зависимости от вашего использования, это может быть или не быть идеальным. Но это, безусловно, работает!


Это неясно в вопросе, но если то, что вы хотите сделать, это полностью "hotswap" класс во время выполнения с вашим собственным, вам не нужно использовать какую-либо библиотеку манипуляции байт-кода. Вместо этого вы можете скомпилировать свой класс отдельно (с тем же пакетом, именем класса и т.д.) И просто вернуть байты для своего нового класса, когда transform вызывается в вашем целевом классе.

Ответ 2

Обычные ClassLoaders не поддерживают определение или изменение классов после их определения. Таким образом, плагин не может изменять поведение структуры, если эта инфраструктура не обеспечивает перехваты для таких настроек.

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

Единственный разумный способ избежать этого (я могу придумать) должен быть первым: если ваш код запускается первым, он может ввести загрузчик классов, который будет использоваться для загрузки фреймворка. Но это означает, что вам нужно будет каким-то образом превратить свой код в цепочку в качестве обертки вокруг фреймворка. Не уверен, насколько это возможно в вашей ситуации.

Обновить в ответ на комментарий:
Чтобы создать класс Loader, который имеет несколько классов, вы должны переопределить его метод loadClass. Если ваше лицензирование позволяет использовать код GPL, вы можете посмотреть как OpenJDK делает это в реализации по умолчанию. Вы только отложите загрузку родительского класса для тех классов, которые вы не хотите скрывать.

Вам все равно придется модифицировать класс после скрытия родительской версии. Возможно, загрузчик классов BCEL может вам помочь. Или вы загружаете класс из файла jar, содержащего измененную версию. Или что-то вроде этого.