Как я писал раньше, всю настоящую работу в maven выполняют плагины. И эти плагины кем-то были разработаны, а следовательно, можно написать и свой собственный плагин.
Подготовка
Лучше всего начинать писать собственный плагин с создания проекта с помощью спецального архетипа:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | mvn archetype:generate -DgroupId=ru.easyjava.maven -DartifactId=name-maven-plugin -Dversion=1 -DarchetypeArtifactId=maven-archetype-plugin -DinteractiveMode=false [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Stub Project (No POM) 1 [INFO] ------------------------------------------------------------------------ [...skip...] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 15.856 s [INFO] Finished at: 2017-09-08T17:48:50+02:00 [INFO] Final Memory: 13M/210M [INFO] ------------------------------------------------------------------------ |
Обратите внимание на имя, name-maven-plugin. По соглашению все плагины должны иметь имя артефакта, оканчивающееся на -maven-plugin.
В созданный пустой архетип полезно будет добавить пару зависимостей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-core</artifactId> <version>3.1.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.maven.plugin-tools</groupId> <artifactId>maven-plugin-annotations</artifactId> <version>3.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.21.0-GA</version> </dependency> </dependencies> |
maven-plugin-api добавляет архетип, а вот остальные необходимо добавить вручную. maven-core используется для доступа к внутренним структурам maven во время выполнения, maven-plugin-annotations позволяет конфигурировать плагин аннотациями в коде. javassist для разработки плагинов не нужен и используется мной только в коде.
Кроме зависимостей к сборке необходимо добавить ещё один плагин, который будет сам собирать дескриптор нашего плагина исходя из аннотаций:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-plugin-plugin</artifactId> <version>3.5</version> <executions> <execution> <id>default-descriptor</id> <phase>process-classes</phase> </execution> </executions> </plugin> </plugins> </build> |
Ну и уровень языка поднять не забываем.
Код плагина
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * Goal which changes a greeted name. */ @Mojo(name = "setname", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true) public class NamingMojo extends AbstractMojo { @Parameter(property = "name") private String name; @Parameter( defaultValue = "${project}", readonly = true ) private MavenProject project; public void execute() throws MojoExecutionException { getLog().info("Updating greeter target"); /* skipped *. } } |
В первую очередь рассмотрим аннотацию @Mojo, которой аннотирован класс плагина:
1 | @Mojo(name = "setname", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true) |
Эта аннотация говорит maven, какой это плагин и как его использовать. name = "setname" создаёт goal и присваивает ей имя setname. defaultPhase = LifecyclePhase.PROCESS_CLASSES указывает, в какую фазу по умолчанию вызывать goal. А threadSafe = true говорит maven, что плагин можно выполнять параллельно с другими плагинами фазы.
Следующий участок кода отвечает за получение параметров из файла pom. Как можно догадаться, значение параметра name будет помещено в переменную name.
1 2 | @Parameter(property = "name") private String name; |
Последняя переменная используется для доступа к параметрам проекта во время сборки:
1 2 | @Parameter( defaultValue = "${project}", readonly = true ) private MavenProject project; |
Что же делает сам плагин? В принципе всё что угодно 🙂 Всё, что будет написано в методе execute, будет исполнено и maven не делает никаких предположений о этом коде. В моём случае, поскольку это демонстрационный плагин, я ищу определённый класс в classpath проекта, ищу в нём определённое поле и вывожу его:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | getLog().info("Updating greeter target"); ClassPool pool = ClassPool.getDefault(); try { for( String cp : project.getCompileClasspathElements()) { pool.insertClassPath(cp); } CtClass ct = pool.get("ru.easyjava.maven.Target"); CtField field = ct.getField("NAME"); getLog().info(String.format("%s %s", name, field.getConstantValue())); } catch (NotFoundException | DependencyResolutionRequiredException e) { getLog().error(e); throw new MojoExecutionException(e.getMessage()); } |
Обратите внимание, как используется инфраструктура maven в коде плагина.
Использование
Чтобы использовать плагин, нам потребуется ещё один проект maven. Я создал его используя стандартный архетип maven-archetype-quickstart и разместил в нём два файла с кодом:
1 2 3 4 5 | public class App { public static void main( String[] args ) { System.out.println(String.format("Hello %s!", Target.NAME)); } } |
1 2 3 4 5 6 | /** * Holds greeting target */ class Target { static final String NAME = "World"; } |
Именно строку NAME из класса Target ищет и выводит мой плагин! Давайте добавим его к сборке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <plugin> <groupId>ru.easyjava.maven</groupId> <artifactId>name-maven-plugin</artifactId> <version>1</version> <configuration> <name>Will greet:</name> </configuration> <executions> <execution> <id>process</id> <phase>process-classes</phase> <goals> <goal>setname</goal> </goals> </execution> </executions> </plugin> |
Я связываю goal setname своего плагина с фазой process-classes и задаю параметру name значение «Will greet:» Теперь достаточно установить плагин командой mvn install и выполнить сборку второго проекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $ mvn package INFO] ------------------------------------------------------------------------ INFO] Building versioned 1 INFO] ------------------------------------------------------------------------ INFO] INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ versioned --- WARNING] Using platform encoding (Cp1251 actually) to copy filtered resources, i.e. build is platform dependent! INFO] skip non existing resourceDirectory \versioned\src\main\resources INFO] INFO] --- maven-compiler-plugin:3.3:compile (default-compile) @ versioned --- INFO] Changes detected - recompiling the module! INFO] Compiling 2 source files to \versioned\target\classes INFO] INFO] --- name-maven-plugin:1:setname (process) @ versioned --- INFO] Updating greeter target INFO] Will greet: World |
Код примера доступен на github.