Напилки бувають різні або оповідання про «напилок» для java програм

У практиці java програміста буває, що дуже хочеться змінити поведінку програми або «подхачить» пару класів без перепакування програми, зібрати метрики або протестувати java додаток в надрах сторонньої бібліотеки або jdbc драйвера без вихідного коду. Існує кілька способів зробити це. Я розповім про open source проект aspectj-scripting, який дозволяє вирішувати такі завдання в jvm.



Розповідь про aspectj-scripting буде у кількох публікаціях. Почнемо з практики! Під катом модифікація поведінки maven-changes-plugin без його перезбирання і перекомпіляції для вивантаження списку задач JIRA у файли xml, json


Але перш ніж рватися в бій, припускаю ви у загальних рисах знайомі з теорією аспектно-орієнтованого програмування, працювали з бібліотекою AspectJ або іншими AOP фреймворками. Цікаво вашу думку в коментарях, наскільки вірно моє припущення і варто мені докладніше описати базові речі в наступних публікаціях.
увага: Aspectj-scripting в поточному вигляді не буде працювати під JEE або OSGI контейнерами. Але при цьому микросервисы і standalone java додатка відмінно уживаються і працюють з його агентом.

Aspectj-scripting — це инструментирующий java агент на основі AspectJ Load-time weaving(LTW) який дозволяє описувати аспекти на скриптовом мовою MVEL, завантажувати і инстанцировать класи з maven репозитаріїв в рантайме, використовувати синтаксис AspectJ для опису pointcut і підтримує life cycle для скриптових аспектів.

Тепер пару слів про деталь для обробки напилком. Одна із зручних функцій maven-changes-plugin — генерація звіту про нових фичах в релізі, виправлені помилки на основі інформації з JIRA, github або track.



І все б влаштовувало в maven-changes-plugin, якщо б цей плагін зберігав не тільки html звіт, але і вихідні дані з яких він стоїть. На основі цих даних з JIRA і логах системи контролю версій для конкретного бранчу можна зробити відмінні release notes. Уникнути ручної роботи і не боятися що щось вислизнуло при складанні звіту тільки на основі JIRA.

Вивчення коду плагіна показало, що раніше, до використання REST сервісів JIRA була можливість генерації такого файлу у форматі XML. Але у версії 2.11 для REST та jql запитів такої можливості немає. Але при цьому з методу org.apache.maven.plugin.jira.AbstractJiraDownloader.getIssueList() повертається список завдань і було б чудово його сериализации та зберегти у файл.

Сказано — зроблено! Пишемо конфігурацію для агента у файлі aspect.xml. А після опису процесу розповім це працює.

<?xml version="1.0" encoding="UTF-8" standalone="так"?>
<configuration>
<aspects>
<name>org.maven.plugin.changes.Dump</name>
<type>AROUND</type>
<pointcut>execution(* org.apache.maven.plugin.jira.AbstractJiraDownloader.getIssueList(..))</pointcut>
<artifacts>
<artifact>com.google.code.gson:gson:2.3.1</artifact>
<classRefs>
<variable>GsonBuilder</variable>
<className>com.google.gson.GsonBuilder</className>
</classRefs>
</artifacts>
<artifacts>
<artifact>com.thoughtworks.xstream:xstream:1.4.8</artifact>
<classRefs>
<variable>XStream</variable>
<className>com.thoughtworks.xstream.XStream</className>
</classRefs>
</artifacts>
<artifacts>
<artifact>commons-io:commons-io:2.4</artifact>
<classRefs>
<variable>FileUtils</variable>
<className>org.apache.commons.io.FileUtils</className>
</classRefs>
</artifacts>
<process>
<expression>

import java.io.File; 

res = joinPoint.proceed();

gson = new GsonBuilder().setPrettyPrinting().create();
FileUtils.writeStringToFile(new File("report.json"), gson.toJson(res));

xstream = new XStream();
xstream.alias("issue", org.apache.maven.plugin.issues.Issue);
FileUtils.writeStringToFile(new File("report.xml"), xstream.toXML(res));

res;
</expression>
</process>
</aspects>
</configuration>


Для віддають перевагу json є можливість описувати конфігурацію в цьому форматі. Мені ж зручніше багаторядкові опису аспектів робити xml.

Викачуємо aspectj-scripting-1.0-agent.jar в директорію, де лежить наш pom.xml файл

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.igor_suhorukov</groupId>
<artifactId>jira_report_example</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<issueManagement>
<system>jira</system>
<url>https://issues.sonatype.org/browse/OSSRH</url>
</issueManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-changes-plugin</artifactId>
<version>2.11</version>
<configuration>
<useJql>true</useJql>
<jiraUser>***РЕАЛЬНИЙ ЛОГІН***</jiraUser>
<jiraPassword>***І ПАРОЛЬ***</jiraPassword>
</configuration>
</plugin>
</plugins>
</build>
</project>


Виставляю в консолі змінну оточення для maven, щоб завантажувався aspectj-scripting агент в jvm і використовував конфігурацію з aspect.xml
export MAVEN_OPTS="-Dorg.aspectj.weaver.loadtime.configuration=config:file:aspect.xml -javaagent:aspectj-scripting-1.0-agent.jar"


Після цього запускаю maven плагін
mvn changes:jira-report


І в тій же директорії після BUILD SUCCESS вже знаходяться два файлу зі списком завдань з JIRA: report.json, report.xml





Отже, як це получилилось. В першу чергу org.maven.plugin.changes.Dump буде викликатися як around аспект при виклику методу getIssueList(), зазначеного в pointcut аспекту.
<type>AROUND</type>
<pointcut>execution(* org.apache.maven.plugin.jira.AbstractJiraDownloader.getIssueList(..))</pointcut>


З допомогою бібліотеки Sonatype Aether з центрального maven репозитарію будуть завантажені 3 артефакту і їх транзитивні залежності com.google.code.gson, com.thoughtworks.xstream commons-io. Для кожного артефакту з допомогою бібліотеки dropship буде створений свій ізольований classloader.

<artifacts>
<artifact>com.google.code.gson:gson:2.3.1</artifact>
<classRefs>
<variable>GsonBuilder</variable>
<className>com.google.gson.GsonBuilder</className>
</classRefs>
</artifacts>
<artifacts>
<artifact>com.thoughtworks.xstream:xstream:1.4.8</artifact>
<classRefs>
<variable>XStream</variable>
<className>com.thoughtworks.xstream.XStream</className>
</classRefs>
</artifacts>
<artifacts>


З класслоудеров завантажуються класи і зберігаються змінні GsonBuilder, XStream і FileUtils. Ці змінні доступні в MVEL скрипті, синтаксис якого сильно нагадує java. Скрипт дозволяє маніпулювати цими класами і сериализации результат методу getIssueList() в XML, JSON формат, записати результати у файл.

res = joinPoint.proceed(); // це результат роботи перехватываемого методу org.apache.maven.plugin.jira.AbstractJiraDownloader.getIssueList()
//тип List<org.apache.maven.plugin.issues.Issue>

gson = new GsonBuilder().setPrettyPrinting().create(); //инстанцируем клас для роботи з json
FileUtils.writeStringToFile(new File("report.json"), gson.toJson(res)); //перетворимо список pojo в json і зберігаємо за допомогою commons-io файл report.json

xstream = new XStream(); //инстанцируем клас для роботи з xml
xstream.alias("issue", org.apache.maven.plugin.issues.Issue); //конфігуруємо його

FileUtils.writeStringToFile(new File("report.xml"), xstream.toXML(res)); //перетворимо список pojo в xml і зберігаємо за допомогою commons-io файл report.xml

res; //повертаємо викликав методом результат з getIssueList()


Агент доступний як артефакт в центральному maven репозитарії

Вказуємо jvm на java агент при старті і його конфігурацію. Конфігурація може завантажуватися з http сервера або файлу. Агент викачує залежності maven репозитарію, инструментирует код і перехоплює виклик методу з плагіна, зберігає список задач JIRA в json і xml форматі. Ось і вся магія!

Сподіваюся, що aspectj-scripting буде корисний для ваших проектів!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.