Maven小记

使用maven也很久了,都知道可以用来compile source code,package jar等,但是一直没有去深究过它到底如何做的,命令行一大堆信息,调用了各个插件,原理是个啥?今天有空,于是探究了一下。

What is Maven

说白了,maven就是一个插件执行框架,maven的所有动作,都是在调用各种plugin的功能。我们使用maven,去调用各种插件,就是为了去使我们的项目构建、测试、编译、打包等等步骤变得更加高效。

Lifecycle

maven将我们对project的构建划分为多个Lifecycle,也就是项目的生命周期。

Lifecycle包含cleandefault以及site。我们一般开发项目最常用的就是clean和default了,一般都是先clean,清除所有编译后的内容以及包,然后执行编译、打包等,而这些就在default生命周期中。

default是最重要的生命周期,项目在这个生命周期里,会被校验、编译、打包、测试、安装等等。具体见官网对Default Lifecycle的描述。

Phase

每个Lifecycle拥有多个Phase(阶段),也就是上面提到的构建、测试、编译等等。

默认的Lifecycle(生命周期)包含了以下Phase(阶段)

  • validate- 验证项目是否正确并且所有必要的信息都可用
  • compile- 编译项目的源代码
  • test- 使用合适的单元测试框架测试编译的源代码。 这些测试不应该要求打包或部署代码
  • package- 获取已编译的代码并将其打包成可分发的格式,例如 JAR。
  • verify- 对集成测试的结果进行任何检查,以确保满足质量标准
  • install- 将包安装到本地存储库中,作为本地其他项目的依赖项
  • deploy- 在构建环境中完成,将最终包复制到远程存储库以与其他开发人员和项目共享。

也就对应了IDEA maven插件中的Lifecycle下拉框:

image-20220326150053626

当然,这些是最常用的,还有更加细化的阶段,在maven源码中,可以参考org.apache.maven.plugins.annotations.LifecyclePhase

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package org.apache.maven.plugins.annotations;

public enum LifecyclePhase
{
// default lifecycle
VALIDATE( "validate" ),
INITIALIZE( "initialize" ),
GENERATE_SOURCES( "generate-sources" ),
PROCESS_SOURCES( "process-sources" ),
GENERATE_RESOURCES( "generate-resources" ),
PROCESS_RESOURCES( "process-resources" ),
COMPILE( "compile" ),
PROCESS_CLASSES( "process-classes" ),
GENERATE_TEST_SOURCES( "generate-test-sources" ),
PROCESS_TEST_SOURCES( "process-test-sources" ),
GENERATE_TEST_RESOURCES( "generate-test-resources" ),
PROCESS_TEST_RESOURCES( "process-test-resources" ),
TEST_COMPILE( "test-compile" ),
PROCESS_TEST_CLASSES( "process-test-classes" ),
TEST( "test" ),
PREPARE_PACKAGE( "prepare-package" ),
PACKAGE( "package" ),
PRE_INTEGRATION_TEST( "pre-integration-test" ),
INTEGRATION_TEST( "integration-test" ),
POST_INTEGRATION_TEST( "post-integration-test" ),
VERIFY( "verify" ),
INSTALL( "install" ),
DEPLOY( "deploy" ),

// clean lifecycle
PRE_CLEAN( "pre-clean" ),
CLEAN( "clean" ),
POST_CLEAN( "post-clean" ),

// site lifecycle
PRE_SITE( "pre-site" ),
SITE( "site" ),
POST_SITE( "post-site" ),
SITE_DEPLOY( "site-deploy" ),

NONE( "" );

}

我们可以针对生命周期的每一个阶段,利用plugin的方式干涉或新建项目build的具体行为。

goal

各个生命周期中,我们都需要完成一些任务,比如compile阶段,需要将java源码编译为class文件,package阶段需要将所有编译完成的内容整合为jar或者war等artifact(阶段性成果)。 而这样的具体行为,在maven中叫做goal,也就是目标,指我们需要在某个阶段完成一件什么样的事情。

plugins

前面已经熟悉了Lifecycle、Phase、goal,知道了maven是如何将项目的构建进行抽象的。光有抽象是没办法完成具体事情的,maven为我们提供了plugin相关接口,我们可以实现这些接口,对其各个阶段的抽象进行具体的实现,去完成各种目标。

调用插件的方式有两种

命令行调用plugin

执行mvn groupId:artifactId:version:goal

可能有点冗长,有以下方式可以进行简化:

  • 如果需要运行本地仓库中某插件的最新版本,则可以省略版本号,命令可以简化为:

    mvn groupId:artifactId:goal

  • 如果插件命名规范,比如${prefix}-maven-plugin格式,亦或者官方的maven-${prefix}-plugin,则可以直接写成:

    mvn ${prefix}:goal,比如mvn spring-boot:repackage

  • 在maven-plugin项目的pom中指定前缀,需要用到maven-plugin-plugin插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <project>
    ...
    <build>
    ...
    <plugins>
    ...
    <plugin>
    <artifactId>maven-plugin-plugin</artifactId>
    <version>2.3</version>
    <configuration>
    ...
    <goalPrefix>my-prefix</goalPrefix>
    </configuration>
    </plugin>
    </plugins>
    </build>
    </project>

    重新install插件后,即可通过mvn my-prefix:goal来进行调用。

通过phase调用插件

pom中定义goal以及phase,然后执行所在的phase,如:mvn compile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<plugin>
<groupId>org.example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<executions>
<execution>
<!-- 因为这个插件配置defaultPhase = LifecyclePhase.COMPILE,所以可以不用指明phase -->
<!-- <phase>compile</phase>-->
<goals>
<goal>hello-plugin</goal>
</goals>
</execution>
</executions>
</plugin>
配置三方插件组

在执行mvn groupId:artifactId:goal时,如果不指定groupId,那么maven默认会去org.apache.maven.plugins还有本地查找插件,但是第三方插件可能会遇到查找不到的情况。可以通过以下方式添加自定义的groupId来方便查找,不用每次都定义第三方groupId:

用户层:${user.home}/.m2/settings.xml,全局:${maven.home}/conf/settings.xml中添加:

1
2
3
<pluginGroups>
<pluginGroup>org.codehaus.modello</pluginGroup>
</pluginGroups>

packaging

也就是pom.xml<packaging>标签,的表示当前project的成品打包格式。包括: pom, jar, maven-plugin, ejb, war, ear, rar。默认为jar

maven生命周期根据packaging配置项的不同,有自己默认的一些plugins以及goals。比如当前project的packaging为jar时,其包含的default生命周期中将会有以下phase,及其需要实现的goal。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<component>
<role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
<role-hint>jar</role-hint>
<implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
<configuration>
<lifecycles>
<lifecycle>
<id>default</id>
<!-- START SNIPPET: jar-lifecycle -->
<phases>
<process-resources>
org.apache.maven.plugins:maven-resources-plugin:2.6:resources
</process-resources>
<compile>
org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
</compile>
<process-test-resources>
org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
</process-test-resources>
<test-compile>
org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile
</test-compile>
<test>
org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
</test>
<package>
org.apache.maven.plugins:maven-jar-plugin:2.4:jar
</package>
<install>
org.apache.maven.plugins:maven-install-plugin:2.4:install
</install>
<deploy>
org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
</deploy>
</phases>
<!-- END SNIPPET: jar-lifecycle -->
</lifecycle>
</lifecycles>
</configuration>
</component>

这个文件在maven-core的META-INF/plexus/default-bindings.xml中。

当然,自己定义的插件并不会自动执行,因为没有对应的default-bindings.xml,能否定义自己的default-bindings.xml以及自己的packaging类型?这个暂时还没研究。

mvn command

常用的mvn命令,具体格式为mvn [options] [<goal(s)>] [<phase(s)>],入参必须有一个goal或者是phase。

如果是goal的话,则会直接执行目标goal,而如果是phase,则会依次执行该phase所属生命周期中的前置phase,比如关于packaging描述中,如果当前project为jar,则package前置phase是test,如果执行mvn package则会自动先执行mvn test

image-20220327164512806

Maven Plugin Development

依赖引入

新建项目,packaging必须配置为maven-plugin,并引入相关依赖。

注意自己开发的插件的命名格式,需要[自己插件的名字]-maven-plugin,不能叫做maven-xxx-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
25
26
27
28
<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>org.example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
<version>1.0-SNAPSHOT</version>
<name>hello-maven-plugin Maven Mojo</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.8.4</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.6.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

常用注解

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Mojo( name = "<goal-name>",
aggregator = <false|true>,
configurator = "<role hint>",
// 执行策略
executionStrategy = "<once-per-session|always>",
inheritByDefault = <true|false>,
// 实例化策略
instantiationStrategy = InstantiationStrategy.<strategy>,
// 如果用户没有在POM中明确设置此Mojo绑定到的phase,那么绑定一个MojoExecution到那个phase
defaultPhase = LifecyclePhase.<phase>,
requiresDependencyResolution = ResolutionScope.<scope>,
requiresDependencyCollection = ResolutionScope.<scope>,
// 提示此Mojo需要被直接调用(而非绑定到生命周期阶段)
requiresDirectInvocation = <false|true>,
// 提示此Mojo不能在离线模式下运行
requiresOnline = <false|true>,
// 提示此Mojo必须在一个Maven项目内运行
requiresProject = <true|false>,
// 提示此Mojo是否线程安全,线程安全的Mojo支持在并行构建中被并发的调用
threadSafe = <false|true> ) // (since Maven 3.0)

// 何时执行此Mojo
@Execute( goal = "<goal-name>", // 如果提供goal,则隔离执行此Mojo
phase = LifecyclePhase.<phase>, // 在此生命周期阶段自动执行此Mojo
lifecycle = "<lifecycle-id>" ) // 在此生命周期中执行此Mojo
public class MyMojo
extends AbstractMojo
{

@Parameter( name = "parameter",
// 在POM中可使用别名来配置参数
alias = "myAlias",
property = "a.property",
defaultValue = "an expression, possibly with ${variables}",
readonly = <false|true>,
required = <false|true> )
private String parameter;

@Component( role = MyComponentExtension.class,
hint = "..." )
private MyComponent component;


@Parameter( defaultValue = "${session}", readonly = true )
private MavenSession session;

@Parameter( defaultValue = "${project}", readonly = true )
private MavenProject project;

@Parameter( defaultValue = "${mojoExecution}", readonly = true )
private MojoExecution mojo;

@Parameter( defaultValue = "${plugin}", readonly = true )
private PluginDescriptor plugin;

@Parameter( defaultValue = "${settings}", readonly = true )
private Settings settings;

@Parameter( defaultValue = "${project.basedir}", readonly = true )
private File basedir;

@Parameter( defaultValue = "${project.build.directory}", readonly = true )
private File target;

public void execute()
{
}
}

maven预置变量:https://web.archive.org/web/20150520200505/https://docs.codehaus.org/display/MAVENUSER/MavenPropertiesGuide

远程调试

业务项目调用maven plugin时,不要使用mvn,而是使用mvndebug命令,会自动起8000端口用于调试。

plugin项目,新建remote jvm debug,并连接8000端口即可。可以提前在需要调试的地方打上断点。

image-20220327165621138

image-20220327165639099