假设读者已经编写好了一个可以在 IDE 中运行的一个 Maven 多模块项目。对于 Maven 多模块项目,应该首先有一个父 POM,它的属性 packaging
为 pom
。而它的一个不成为其它模块的父模块的子模块,其属性 packaging
应该为 jar
或 war
。
将上面的 Maven 项目只打成一个 JAR 包,可以使用插件 maven-assembly-plugin
。它有一个目标 single 可以完成此任务。使用该插件打包出来的 JAR 包将会包含完整的依赖,可以在 CMD 下直接运行。
(该插件网址:https://maven.apache.org/plugins/maven-assembly-plugin/plugin-info.html)
使用该插件的具体方法为,在需要被打包的模块(这里选择的是父模块)中加入如下配置:
<project...>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-assembly-pluginartifactId>
<version>3.3.0version>
<executions>
<execution>
<id>make-assemblyid>
<phase>packagephase>
<goals>
<goal>singlegoal>
goals>
execution>
executions>
<configuration>
<archive>
<manifest>
<mainClass>包名.类名mainClass>
manifest>
archive>
<outputDirectory>${project.build.directory}/#maven-assembly-pluginoutputDirectory>
<descriptorRefs>
<descriptorRef>jar-with-dependenciesdescriptorRef>
descriptorRefs>
configuration>
plugin>
plugins>
build>
project>
如果读者想偷懒,可以只更改上述配置的三处位置:
:将其改为读者自己模块的程序运行入口所在的类的类名。
:将其改为读者当时打包时,本插件 maven-assembly-plugin
的最新版本。
:将其改为读者自己喜欢的输出目录。
如果读者想要打包后的目录更有条理,可以去掉上面
中的
,而改用
。这里对这方面不做详细介绍,有兴趣的读者可以查阅其它资料。
上面的配置完成之后,在进行该配置的模块目录下执行如下命令:mvn package assembly:single
。这会为其下的各个模块各做一次打包,生成的 JAR 包将输出至每个模块文件夹 target 中的上述设置中的指定目录。这里要注意的是,此插件会根据依赖关系进行打包,只有依赖各个模块的那个“核心模块”下的 JAR 包中才有完整的依赖。如果一个模块不依赖于其它模块,则此模块生成的 JAR 包将不包含其它模块。因此,父 POM 生成的 JAR 包里不会有任何有意义的内容。读者需要自行找到自己“核心模块”下的 JAR 包。
另外,建议生成 JAR 包前先清理一下工作空间,所以最好先输入命令 mvn clean
。不过,这两个命令可以合成一个:
如果在此基础上,想跳过单元测试,可以使用如下命令:
【注意】
原 Maven 中跳过单元测试的命令是:mvn package -Dmaven.test.skip=true
。但如果不小心使用了 IntelliJ IDEA 中的默认终端来运行 Maven 命令,则此命令可能会报错。这需要加在此参数外加单引号。更详细的信息,可见笔者的另一篇博客:
解决IntelliJ IDEA下Maven报错Unknown lifecycle phase “.test.skip=true“:
https://blog.csdn.net/wangpaiblog/article/details/121020069
【说明】
一般来说,父 POM 中是不编写任何有实际意义的 Java 代码的。在父 POM 中,一般使用
,而不是
。因此,父 POM 中不会包含任何依赖,所以父 POM 生成的 JAR 包里不会有任何实际的内容。读者需要自行找到一个通过直接和间接依赖能依赖其它所有有实际内容的模块的“核心模块”,这个“核心模块”中的依赖应该是完整的,且会包含所有完整代码、资源文件。“核心模块”往往是一种程序入口模块。
上面使用的元素
是可以被继承的,这就是为什么将其置于父 POM 中。这样一来,父 POM 中的各个子模块就相当于都使用了此元素,这样在父 POM 中运行打包命令,各个子模块就会各自都进行一次打包。
如果没有更改笔者前面所说的配置,打包后的 JAR 包将位于每个模块的文件夹 target 中,同时 JAR 包的名称为 模块名
-模块版本号
-jar-with-dependencies。这是因为上面使用了
。但是,元素
中的内容为专有内容,不要随意更改为其它内容。
如果读者想自定义名称,可以使用标签使用标签
。如:
<configuration>
<finalName>NameYouLikefinalName>
configuration>
如果选择本插件,则 Maven 多模块中不能出现多个同名同路径的资源文件。如果多模块中各自使用了同名同路径的资源文件,则插件 maven-assembly-plugin
将以不覆盖的方式进行合并,这将会让这些资源文件最终只保留一个。
那么,资源文件将保留哪一个呢?答案很奇怪。对于使用了本插件的本模块和此同名同路径的资源文件,保留的是本模块直接依赖的第一个含此同名资源文件的那个模块的。如果模块依赖链很长,则这个文件不会是依赖链中最初被依赖的那个模块的,也不会是本模块的。出现这种情况时很麻烦,所以建议不要在 Maven 多模块中使用多个同名同路径的资源文件。也有人选择令每个模块的该同名文件的内容保持一样,这样就不需要关注其保留了哪个文件。但这种方法非常笨拙,最好还是听从笔者的建议。
【踩坑提醒】
不要使用命令 mvn package
进行打包。这样生成的 JAR 包是不完整的。
不要使用命令 mvn assembly:single
进行打包。这样生成的 JAR 包是不完整的,且会有如下报错:
Cannot include project artifact: XXX; it doesn't have an associated file or directory.
不要使用 IntelliJ IDEA 中内置的界面选项进行打包。这样生成的 JAR 包是不完整的,且会有如下报错:
Cannot include project artifact: XXX; it doesn't have an associated file or directory.
不要去掉配置中的
,否则会有报错:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-assembly-plugin:3.3.0:single (make-assembly) on project XXX: Failed to create assembly: Error creating asse
mbly archive jar-with-dependencies: archive cannot be empty -> [Help 1]
如果完成了上面的操作,打出来的应该是一个可以在 CMD 中单独运行的 JAR 包。在 CMD 中运行 JAR 包的方法是,使用如下命令。
将上面的 Maven 项目只打成一个 JAR 包,还可以使用插件 maven-shade-plugin
。使用该插件打包出来的 JAR 包将会包含完整的依赖,可以在 CMD 下直接运行。
(该插件网址:https://maven.apache.org/plugins/maven-shade-plugin/)
使用该插件的具体方法为,在需要被打包的模块(这里选择的是父模块)中加入如下配置:
<project...>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-shade-pluginartifactId>
<version>3.2.4version>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>shadegoal>
goals>
<configuration>
<shadedArtifactAttached>trueshadedArtifactAttached>
<shadedClassifierName>shaded-with-dependenciesshadedClassifierName>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>包名.类名mainClass>
transformer>
transformers>
<outputDirectory>${project.build.directory}/#maven-shade-pluginoutputDirectory>
configuration>
execution>
executions>
plugin>
plugins>
build>
project>
如果读者想偷懒,可以只更改上述配置的三处位置:
:将其改为读者自己模块的程序运行入口所在的类的类名。
:将其改为读者当时打包时,本插件 maven-shade-plugin
的最新版本。
:将其改为读者自己喜欢的最终生成的 JAR 包名
:将其改为读者自己喜欢的输出目录。
如果读者需要在打包时解决 JAR 包冲突,资源文件冲突等问题,此插件可以解决。这里不对这方面做详细介绍,有兴趣的读者可以查阅其它资料。
上面的配置完成之后,在进行该配置的模块目录下执行如下命令:mvn package
。这会为其下的各个模块各做一次打包,生成的 JAR 包将输出至每个模块文件夹 target 中的上述设置中的指定目录。这里要注意的是,此插件会根据依赖关系进行打包,只有依赖各个模块的那个“核心模块”下的 JAR 包中才有完整的依赖。如果一个模块不依赖于其它模块,则此模块生成的 JAR 包将不包含其它模块。因此,父 POM 生成的 JAR 包里不会有任何有意义的内容。读者需要自行找到自己“核心模块”下的 JAR 包。
另外,建议生成 JAR 包前先清理一下工作空间,所以最好先输入命令 mvn clean
。不过,这两个命令可以合成一个:
如果在此基础上,想跳过单元测试,可以使用如下命令:
【注意】
原 Maven 中跳过单元测试的命令是:mvn package -Dmaven.test.skip=true
。但如果不小心使用了 IntelliJ IDEA 中的默认终端来运行 Maven 命令,则此命令可能会报错。这需要加在此参数外加单引号。更详细的信息,可见笔者的另一篇博客:
解决IntelliJ IDEA下Maven报错Unknown lifecycle phase “.test.skip=true“:
https://blog.csdn.net/wangpaiblog/article/details/121020069
【说明】
一般来说,父 POM 中是不编写任何有实际意义的 Java 代码的。在父 POM 中,一般使用
,而不是
。因此,父 POM 中不会包含任何依赖,所以父 POM 生成的 JAR 包里不会有任何实际的内容。读者需要自行找到一个通过直接和间接依赖能依赖其它所有有实际内容的模块的“核心模块”,这个“核心模块”中的依赖应该是完整的,且会包含所有完整代码、资源文件。“核心模块”往往是一种程序入口模块。
上面使用的元素
是可以被继承的,这就是为什么将其置于父 POM 中。这样一来,父 POM 中的各个子模块就相当于都使用了此元素,这样在父 POM 中运行打包命令,各个子模块就会各自都进行一次打包。
如果没有更改笔者前面所说的配置,打包后的 JAR 包将位于每个模块的文件夹 target 中,同时 JAR 包的名称为 模块名
-模块版本号
-jar-with-dependencies。这是因为上面使用了
。读者也可以自由指定输出的 JAR 包的后缀。
如果仅仅使用笔者上面关于插件 maven-shade-plugin
的简单配置,则这不能解决 Maven 多模块中多个同名同路径的资源文件的冲突的问题。一旦发生了冲突,这些同名同路径的文件只会保留其中一个。对于使用了本插件的本模块和此同名同路径的资源文件,如果只使用笔者上面的简单配置,然后多模块中各自使用了同名同路径的资源文件,则插件 maven-assembly-plugin
将只保留本模块的该资源文件。
建议不要在 Maven 多模块中使用多个同名同路径的资源文件。也有人选择令每个模块的该同名文件的内容保持一样,这样就不需要关注其保留了哪个文件。但这种方法非常笨拙,最好还是听从笔者的建议。
如果完成了上面的操作,打出来的应该是一个可以在 CMD 中单独运行的 JAR 包。在 CMD 中运行 JAR 包的方法是,使用如下命令。