使用maven也很久了,都知道可以用来compile source code,package jar等,但是一直没有去深究过它到底如何做的,命令行一大堆信息,调用了各个插件,原理是个啥?今天有空,于是探究了一下。
What is Maven
说白了,maven就是一个插件执行框架,maven的所有动作,都是在调用各种plugin的功能。我们使用maven,去调用各种插件,就是为了去使我们的项目构建、测试、编译、打包等等步骤变得更加高效。
Lifecycle
maven将我们对project的构建划分为多个Lifecycle,也就是项目的生命周期。
Lifecycle包含clean
,default
以及site
。我们一般开发项目最常用的就是clean和default了,一般都是先clean,清除所有编译后的内容以及包,然后执行编译、打包等,而这些就在default生命周期中。
default
是最重要的生命周期,项目在这个生命周期里,会被校验、编译、打包、测试、安装等等。具体见官网对Default Lifecycle的描述。
Phase
每个Lifecycle拥有多个Phase(阶段),也就是上面提到的构建、测试、编译等等。
默认的Lifecycle(生命周期)包含了以下Phase(阶段)
validate
- 验证项目是否正确并且所有必要的信息都可用
compile
- 编译项目的源代码
test
- 使用合适的单元测试框架测试编译的源代码。 这些测试不应该要求打包或部署代码
package
- 获取已编译的代码并将其打包成可分发的格式,例如 JAR。
verify
- 对集成测试的结果进行任何检查,以确保满足质量标准
install
- 将包安装到本地存储库中,作为本地其他项目的依赖项
deploy
- 在构建环境中完成,将最终包复制到远程存储库以与其他开发人员和项目共享。
也就对应了IDEA maven插件中的Lifecycle下拉框:
当然,这些是最常用的,还有更加细化的阶段,在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 { 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" ),
PRE_CLEAN( "pre-clean" ), CLEAN( "clean" ), POST_CLEAN( "post-clean" ),
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> <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> <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> </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
。
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> )
@Execute( goal = "<goal-name>", // 如果提供goal,则隔离执行此Mojo phase = LifecyclePhase.<phase>, // 在此生命周期阶段自动执行此Mojo lifecycle = "<lifecycle-id>" ) 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端口即可。可以提前在需要调试的地方打上断点。