Задумывались ли вы, что делать, если вам нужно «просто небольшое изменение» для библиотеки третьей части, от которой зависит ваш проект? В этом посте описывается, как использовать Maven и Javassist для получения зависимости от вашего проекта, инструктировать его для изменения его поведения, переупаковывать его и выпускать как артефакт с другим именем (так что вы меня зависите от my-customized- lib вместо lib)
Процесс выглядит следующим образом:
- Фаза process-sources — maven-dependency-plugin распаковывает зависимость к классам /
- Фазовая компиляция (неявная) — компилировать код манипуляции с байт-кодом
- Фаза-классы процесса — exec-maven-plugin выполняет скомпилированный инструментарий Javassist для изменения распакованных классов
- Фазовый тест — запустить тесты на инструментальном коде
- Фазовый пакет — пусть maven-jar переупаковывает инструментированные классы, исключая самого инструктора
Почему: дело Введение
Модификация двоичных файлов сторонних библиотек, безусловно, уродливая вещь, но иногда это самый выполнимый способ удовлетворить потребность. Так было с моим JSF EL Validator, который повторно использует существующие реализации EL с подключенными пользовательскими переменными и определителями свойств (возвращая ложные значения ожидаемых типов, потому что реальные объекты недоступны во время проверки). Проблема заключалась в том, что спецификация EL требует оценки коротких замыканий выражений?: Branch и of / boolean, в то время как для проверки выражений мне потребовались все их части для оценки. Оказалось, что единственным возможным решением является модификация реализаций EL для оценки всех дочерних элементов узла логического типа / выбора.
Как: Javassist, Dependency и Exec Плагины
Я использовал распространенные плагины Maven для извлечения и распаковки зависимости, для выполнения кода инструментария, а также для повторной упаковки классов и выпуска их в качестве артефакта с другим именем. Инструментарий реализован с использованием небольшого количества Java-кода, использующего Javassist для фактического изменения распакованных файлов классов.
Объяснение будет следовать этапам жизненного цикла Maven, с которыми связаны отдельные операции.
0. Настройте проект, чтобы сделать контрольно-измерительные приборы
Сначала мы создаем новый проект (или, скорее всего, модуль Maven основного проекта) для извлечения, изменения, упаковки и выпуска зависимостей, которые нам нужно настроить. Важной вещью в проекте является только POM, который связывает все плагины и фазы вместе, и код Javassist, который выполняет инструментарий.
Примером является jasper-el-customized-jsf12 , который изменяет org.apache.tomcat: jasper-el и выпускает его как jasper-el-custom-jsf12. Обратите внимание, что я добавил jasper-el в качестве явной зависимости к проекту — это сделано для того, чтобы моя среда IDE знала об этом и чтобы Maven мог скомпилировать мой вспомогательный класс инструментария (который обращается к классам jasper-el). Теоретически это не должно быть необходимым, так как классы будут доступны на этапе process-sources, но это потребует некоторой настройки путей к классам компилятора и IDE, хотя это будет более чистый подход (поскольку я бы избежал зависимости дважды от путь к классам: один раз явно как артефакт .jar и один раз как распакованные классы).
1. Фаза process-sources — плагин maven-dependency-plugin распаковывает зависимость
Сначала нам нужно получить и распаковать зависимость, чтобы Javassist мог с ней работать. Проще всего распаковать его в target / classes /, чтобы тесты и плагин JAR имели к нему доступ без какой-либо конфигурации их пути к классам.
(Обратите внимание, что код инструментария может быть скомпилирован без зависимости от модифицируемого артефакта, поскольку код для вставки / изменения может быть представлен просто как String.)
Вот как это сделать:
org.apache.maven.plugins maven-dependency-plugin 2.3 unpack unpack org.apache.tomcat jasper-el ${jasper.jsf12.version} META-INF/** ${jasperel.unpacked.directory} true
2. Фазовая компиляция (неявная) — компилировать код манипуляции с байт-кодом
Здесь не нужно никаких настроек, mvn compile скомпилирует код инструментария Javassist, как и любой другой код Java.
Сначала вам, конечно, нужно написать код инструментария. Я решил использовать Javassist, потому что он не вводит никаких новых зависимостей во время выполнения в модифицированный артефакт. (Прочитайте мое недавнее введение в Javassist, если вы чувствуете необходимость.)
См. JavassistTransformer.java для деталей реализации, код довольно прост и понятен. Несколько комнетов:
- JavassistTransformer имеет main (), поэтому он может быть запущен плагином exec
- ClassPool Javassist проинструктирован искать классы для инструмента в папке target / classes
- Мы сообщаем Javassist, какие классы он должен найти и какие их методы он должен модифицировать (через insertBefore), а затем сохраняем измененные классы.
- Я свернул код для внедрения в преобразователь, где он может быть представлен только в виде строки, и просто вставил делегирование обработки в вспомогательный класс GetValueFix.java , который представляет собой обычный код Java с использованием классов зависимости, и скомпилировал от Javac и добавил в модифицированный кувшин.
3. Фаза-классы процесса — exec-maven-plugin выполняет скомпилированный инструментарий Javassist для изменения распакованных классов
Скомпилированный код инструментария должен быть выполнен — поэтому у него есть основной метод — для фактического изменения распакованных классов. Это легко достигается с помощью плагина exec:
org.codehaus.mojo exec-maven-plugin 1.2.1 java process-classes net.jakubholy.jeeutils.jsfelcheck.jasperelcustomizer.instrumenter .JavassistTransformer ${jasperel.unpacked.directory} true org.javassist javassist ${javassist.version}
(Я должен указать Javassist как явную зависимость, потому что его область как зависимость проекта «предусмотрена» и, следовательно, плагин exec игнорирует ее.)
4. Фазовый тест — запустить тесты на инструментированном коде
Вы не ожидаете, что я не буду тестировать свой код, верно?
Тестирование не требует специальной настройки, поскольку инструментированные классы для тестирования уже находятся в target / classes. Если бы мы хотели, чтобы они были где-то еще, мы бы просто предоставили конфигурацию надёжного плагина с помощью AdditionalClasspathElements / AdditionalClasspathElement.
(Вы заметите, что я действительно сделал это в POM, хотя это и не нужно, учитывая мое прямое использование цели / классов.)
Кстати, если вам интересно, как тесты, написанные на Groovy, компилируются и выполняются, обратите внимание, что я настроил это в родительском POM . (Который является единственным элементом конфигурации, имеющим какое-либо влияние на этот модуль.)
5. Фазовый пакет — пусть maven-jar перепакует инструментированные классы
Опять же, мы могли бы оставить плагин JAR с его конфигурацией по умолчанию, так как все необходимое находится в target / classes, но я предпочитаю явно указывать classesDirectory и исключать код инструментария (включая помощник GetValueFix).
ПОМ
Вы можете проверить полный файл pom.xml на GitHub.
Альтернатива: переименование пакета
Если вы хотите изменить корневой пакет измененной зависимости, вы можете сделать это с помощью одного из переименователей пакетов для Maven , f.ex. Uberize плагин , но тогда вам нужно будет подключить в обработке он делает на самом деле выполнить приборы, например , путем реализации пользовательских Uberize трансформатора (который , вероятно , должны были бы быть распределены в качестве независимого artifcat своей собственной для плагина , чтобы иметь возможность используй это).
Резюме
Я показал подход для настройки набора плагинов Maven для распаковки, инструментов и повторной упаковки зависимости и кода для выполнения фактического инструментирования с использованием Javassist. Подход работает, но его, безусловно, можно улучшить, например, я бы предпочел распаковывать классы зависимости в свою собственную папку, а не в target / classes, и я бы также предпочел не указывать зависимость явно в Секция зависимостей, поскольку это создает дублирование его исходных классов (из jar артефакта зависимости) и измененных (локально распакованных) классов на пути к классам.
От http://theholyjava.wordpress.com/2011/10/19/hacking-a-maven-dependency-with-javassist-to-fix-it/