Вопрос по playframework, javassist, classloader, runtime, java – Заменить содержимое некоторых методов во время выполнения

22

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

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

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

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

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

Спасибо за помощь !

@ Code-Guru Нет, не могу, это статический класс. Так что его методы напрямую вызываются через класс Fabien Henon
Можете ли вы написать подкласс класса, который вы хотите изменить? Code-Apprentice
@Джеффри, я бы мог, но я бы хотел написать «полезный». плагин для сообщества, поэтому я хотел бы избежать изменения исходного кода Fabien Henon
Играть! с открытым исходным кодом. Вы можете изменить метод непосредственно в источнике и создать свой собственный jar для использования. Jeffrey

Ваш Ответ

2   ответа
3

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

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

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

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

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

Я не могу быть там первым. Однако вы сказали что-то о «сокрытии» некоторые занятия. Вы имеете в виду, что я мог бы создать другой classLoader, сделать так, чтобы он наследовал от classLoader фреймворка, и когда запрашивается класс, который я хочу улучшить, я загружаю его сам? (и если спросят другой класс, я позволю суперклассу загрузить его?). Будет ли это работать? Если да, как я могу сделать, чтобы загрузить класс? Имеете копию этого класса в другом месте и загружаете эту копию? Находить класс, улучшать его и возвращать вновь созданный класс? Fabien Henon
@FabienHenon, любой новый загрузчик влияет только на классы, которые вы явно загружаете через него, или на те классы, на которые они ссылаются. Таким образом, вам также придется заменить всю структуру. Что не то, что вы хотите, насколько я вас понимаю. Тип модификации, который вы имеете в виду, называется & # x201C; monkey-patching & # x201D; в таких языках, как Python. Там былоa question об этом в отношении java, но я сомневаюсь, что решение будет применимо и к вашему делу.
Я попытался добавить журнал в classLoader, определенный игровой средой, но он не вызывается (метод loadClass), когда используется класс, который я хочу улучшить. Это потому, что он загружен из системного загрузчика классов? Как я мог тогда сделать? Это потому, что это класс только со статическими методами? Fabien Henon
Таким образом, невозможно заменить содержимое метода, если я не могу заменить classLoader до каркаса? Я не могу просто установить новый загрузчик? Или освободить системный загрузчик? Fabien Henon
@FabienHenon, вы можете запросить каждый класс о загрузчике классов, с которым он был загружен, используяClass.getClassLoader(), Вы можете использовать это, чтобы увидеть, кто загрузил данный класс. Я думаю, это будет системный загрузчик классов. Как я уже сказал, выhave перед рамкой решить, какой загрузчик классов будет использован.
7

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

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

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

Make a retransforming program the "regular" -javaagent way, with premain et al Rename premain to agentmain Create a temporary JAR file containing your agent classes and having a manifest pointing Agent-Class to your agent (agentmain-containing) class, and Can-Retransform-Classes set to true Obtain the PID of the target JVM (potentially the same process), and attach the temporary jar to it

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

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

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

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

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

Похожие вопросы