Как я писал раньше, всю настоящую работу в 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.