Maven的构建生命周期由多个阶段组成,例如清理、编译、测试、打包、安装和部署,但这些阶段本身并不执行实际构建动作。真正承担任务的是绑定在阶段上的插件目标,而pom.xml中的<build>元素正是声明这些插件及其配置的位置。插件配置的层级和继承方式会直接影响最终构建结果,这也是为什么同样执行mvn package,不同项目可能产生完全不同的行为。

在正式配置插件之前,需要先理解插件坐标、目标、配置和执行的几个概念。一个插件由groupId、artifactId和version唯一标识,例如编译插件maven-compiler-plugin。插件内部可以包含多个目标,每个目标对应一个可执行任务,比如compiler插件中的compile和testCompile分别用于编译主代码和测试代码。configuration用来设置插件目标运行时的参数,executions用来将目标绑定到生命周期阶段并可以添加专属配置。
一、plugins与pluginManagement的区别
<build>下通常可以配置<plugins>和<pluginManagement>两个子元素,它们职责不同。<plugins>中声明的插件会真正参与到当前模块的构建过程中,直接生效。<pluginManagement>则更像是插件的版本和配置模板,它不会主动触发任何插件执行,只在使用该插件的模块中提供默认版本和默认配置。当子模块继承父POM时,如果子模块也声明了同一个插件,Maven会合并父POM的pluginManagement配置与子模块的plugins配置。
这种设计的最大好处是在多模块项目中统一管理插件版本。父POM通过pluginManagement锁定所有插件的版本和通用参数,子模块只需要写groupId和artifactId即可,不必关心版本号。比如父POM中声明maven-compiler-plugin的版本为3.11.0并设置Java版本,子模块如果需要额外配置只需要在plugins中重新声明该插件,Maven会合并父模板参数,子模块可以覆盖部分参数而不必重复版本。如果插件只在pluginManagement中声明而没有任何模块在plugins中引用,该插件不会被执行。
需要注意,pluginManagement本身不会传递执行绑定。父POM中pluginManagement里定义的executions不会自动在子模块生效,只有子模块在plugins中声明该插件后,executions才会被继承。因此仅想统一版本时使用pluginManagement,想让插件真正执行必须在plugins中声明。
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
二、常用核心插件配置详解
maven-compiler-plugin
编译插件是使用频率最高的插件之一。它负责把Java源文件编译成class文件。默认情况下Maven会使用一个较低版本执行编译,这在现代Java项目中往往会导致版本不匹配。建议显式配置source和target,它们分别表示源代码使用的Java版本和生成字节码的目标版本。从Java 9开始还可以使用release参数同时控制编译器的-source、-target和-bootclasspath,避免API版本不一致问题。
另一个常见参数是encoding,用来指定源码文件的字符编码。如果项目源码使用UTF-8而平台默认编码是GBK,编译时可能出现乱码或不可映射字符错误。生产环境建议始终显式设置UTF-8。还可以通过compilerArgs传入额外的javac参数,例如开启参数名称保留-parameters。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
maven-surefire-plugin
surefire插件专门用于运行单元测试,它默认绑定在test阶段。很多开发者遇到过测试被跳过或者测试类没有被识别的情况,通常与surefire的includes和excludes配置有关。默认情况下surefire会自动识别*Test.java、Test*.java、*Tests.java和*TestCase.java这些命名模式的测试类。但如果测试类使用了其他命名方式,就需要通过includes显式指定。
跳过测试是CI流程中的常见需求。可以通过配置skipTests为true来跳过测试执行但保留测试编译,或者设置skip为true完全跳过测试编译和执行。还可以在命令行用-DskipTests临时控制。需要注意这两个参数的区别,skipTests只是不运行测试,skip会同时跳过测试代码编译,可能导致打包产物中没有测试相关class。
在测试阶段需要传递系统属性时,可以使用systemPropertyVariables。这种方式比在pom中配置argLine更直观,适合向测试代码注入环境参数。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/Test*.java</include>
</includes>
<systemPropertyVariables>
<env>test</env>
</systemPropertyVariables>
</configuration>
</plugin>
maven-jar-plugin
jar插件用来将项目打包成JAR文件。最基本的配置是finalName,它决定生成文件的名称。如果希望把主类写入清单文件,以便通过java -jar直接运行,需要配置archive下的manifestEntries,或者使用专门生成可执行JAR的插件如maven-shade-plugin。对于普通JAR,设置Main-Class即可生成带主类信息的MANIFEST.MF。
有时打包时需要排除某些文件或目录,可以使用excludes。例如排除所有测试相关的配置或者本地开发文件。jar插件还支持includes来限定只打包特定内容。需要处理资源过滤时,如果JAR内的资源文件需要替换Maven属性占位符,可以结合maven-resources-plugin开启filtering,而不是在jar插件里直接处理。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<finalName>${project.artifactId}-${project.version}</finalName>
<archive>
<manifestEntries>
<Main-Class>com.example.Main</Main-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
spring-boot-maven-plugin
对于Spring Boot项目,打包成可执行JAR需要使用spring-boot-maven-plugin。它会在package阶段重新打包,将应用及其所有依赖组织成可以通过java -jar启动的结构。该插件的repackage目标负责生成可执行归档文件,通常会覆盖原始JAR或者生成以.original为后缀的原始JAR。配置mainClass可以指定启动类,如果项目中有多个包含main方法的类,建议显式设置避免构建失败。
该插件还支持在打包时排除某些依赖,例如将provided范围的依赖排除在可执行JAR之外。通过excludes可以控制哪些依赖不会被嵌套进最终归档。对于多模块项目,需要把该插件配置在启动模块中,不要在每个子模块都执行repackage,否则可能产生大量无用的可执行JAR。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<mainClass>com.example.Application</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
三、executions与生命周期阶段绑定
插件默认绑定是Maven预先定义好的,比如compiler的compile目标绑定在compile阶段,surefire的test目标绑定在test阶段。但如果需要让某个插件目标在其他阶段运行,或者同一个插件执行多次,就需要使用<executions>。每个execution可以指定一个唯一id、一组goals、一个phase以及可选的configuration。将goal绑定到phase后,Maven在到达该阶段时会执行这个goal。
一个典型场景是让maven-resources-plugin在prepare-package阶段复制某些资源。配置execution时,id不能重复,否则Maven会报错。phase可以省略,如果省略则使用插件元数据中定义的默认阶段;如果该goal没有默认阶段,必须显式指定phase。configuration写在execution内部只对该次执行有效,不会影响插件在其他阶段的默认执行。
另一个常见用途是让maven-antrun-plugin在某个阶段运行自定义脚本,或者绑定maven-surefire-plugin的integration-test目标到integration-test阶段来执行集成测试。通过executions灵活组合插件目标,可以让构建流程适应更复杂的项目需求。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>copy-config</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/config</outputDirectory>
<resources>
<resource>
<directory>src/main/conf</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
四、多模块项目中的插件继承与合并规则
多模块项目中,父POM里的build配置会被子模块继承,但合并规则并不简单。对于<plugins>中的插件,子模块如果声明了相同坐标的插件,Maven会合并两者的configuration。子模块配置优先级更高,父POM中的参数如果子模块没有覆盖则继续生效。但executions的合并更加复杂:子模块的executions会与父POM的executions按id合并,相同id的执行会覆盖,不同id的执行会追加。
如果父POM使用<pluginManagement>,子模块不需要声明版本就能继承配置。但是执行绑定的继承有一个前提:子模块必须在<plugins>中声明该插件,否则pluginManagement中定义的executions不会自动运行。这就是为什么很多项目在父POM的pluginManagement里配置了executions却不生效的原因。
为了避免子模块之间的插件配置互相干扰,建议在父POM中只通过pluginManagement统一版本和通用参数,具体执行绑定和个性化配置放在子模块的plugins中。对于需要所有子模块都执行的插件,可以直接在父POM的plugins中声明,但要注意如果子模块不需要该插件,可能会产生冗余执行。此时可以结合<inherited>false</inherited>阻止插件继承,让特定子模块排除某个插件。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<inherited>false</inherited>
</plugin>
五、常见配置错误与排查思路
插件配置错误通常表现为构建日志中出现目标执行失败、参数不生效或者插件版本冲突。排查时应首先检查插件的groupId和artifactId是否正确,以及version是否明确。如果使用IDE的Maven面板执行构建,建议先用命令行mvn -X输出调试日志,确认实际生效的插件版本和配置合并结果。Maven输出的Effective POM是很有用的工具,执行mvn help:effective-pom可以看到父POM与子POM合并后的完整配置,能快速定位插件参数被覆盖或丢失的问题。
另一个常见问题是插件目标没有在预期阶段执行。可以通过mvn help:describe -Dplugin=groupId:artifactId:version -Ddetail查看插件的默认绑定和目标描述,确认目标本身是否有默认阶段。如果execution中同时配置了phase和goal,但phase拼写错误,Maven会提示生命周期阶段不存在。检查execution id是否重复也很重要,重复id会导致构建失败。
还有一种情况是插件配置写在pluginManagement里,但期望它直接生效。pluginManagement的模板特性容易让开发者误以为配置会自动执行,实际上它只负责版本和配置的继承。如果插件从未在plugins中声明,构建时不会有任何反应。修改配置后建议执行mvn clean verify并查看构建日志,确保插件日志出现在预期阶段。
Maven pom.xmlbuild plugins插件配置修改时间:2026-09-30 13:52:59