Статьи

Взлом Maven Dependency с Javassist, чтобы исправить это

Задумывались ли вы, что делать, если вам нужно «просто небольшое изменение» для библиотеки третьей части, от которой зависит ваш проект? В этом посте описывается, как использовать Maven и Javassist для получения зависимости от вашего проекта, инструктировать его для изменения его поведения, переупаковывать его и выпускать как артефакт с другим именем (так что вы меня зависите от my-customized- lib вместо lib)

Процесс выглядит следующим образом:

  1. Фаза process-sources — maven-dependency-plugin распаковывает зависимость к классам /
  2. Фазовая компиляция (неявная) — компилировать код манипуляции с байт-кодом
  3. Фаза-классы процесса — exec-maven-plugin выполняет скомпилированный инструментарий Javassist для изменения распакованных классов
  4. Фазовый тест — запустить тесты на инструментальном коде
  5. Фазовый пакет — пусть 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/