Maven
提供了多种打包方式,其中常见的包括三种:jar
、shade
、assembly
。下面是它们的详细比较:
Jar
打包方式:
- 描述:这是最常见的打包方式,它创建一个标准的
Java JAR
文件。 - 优点:简单直接,适用于大多数简单项目。
- 缺点:不能包含项目的依赖,如果项目有外部依赖,用户必须手动将它们添加到类路径中。
Shade
打包方式:
- 描述:
Maven Shade
插件允许创建一个可执行的JAR
文件,其中包含所有依赖。 - 优点: 生成一个独立的可执行
JAR
,无需用户手动添加依赖。 - 缺点: 可能会导致
JAR
文件较大,不适合所有项目。
Assembly
打包方式:
- 描述:
Maven Assembly
插件提供了一种更灵活的打包方式,允许创建各种自定义分发包。 - 优点:可以根据项目的需要创建定制的分发包,非常灵活。
- 缺点:配置相对复杂,适用于需要高度定制化的项目。
总结 :
Jar
方式适用于简单项目,但对于有依赖的项目需要手动处理依赖; 默认的打包方式,用来打普通的project JAR
包。Shade
方式生成可执行JAR
,但可能导致文件较大; 用来打可执行jar
包,也就是所谓的fat JAR
包。Assembly
方式最灵活,可以根据项目需求创建定制分发包; 自定义的打包结构,也可以定制依赖项等。
1.问题
Maven
默认将依赖安装到 ~/.m2/repository
下,如果一个 java
项目没有作额外的打包配置,那么在打出来的 jar
包,是无法直接运行第三方依赖的。
譬如,很常见的错误就是:java.lang.NoClassDefFoundError
。
出现这个问题原因,是 classpath
配置错误,需要手动指定 classpath
路径,才能正常加载第三方依赖(也就是说,所以的 jar
包是在 ~/.m2/repository
下,但 Maven
打包时没有将这个路径添加到 classpath
中)。
这一节,我们先说明一个较为笨的方式,后续章节,会通过介绍工程化插件的方式,解决这个问题。
# 获取 classpath
CLASS_PATH=$(mvn dependency:build-classpath | grep -v "\[INFO\]")
# 运行
java -cp "target/my-app-1.0.0.jar:$CLASS_PATH" com.example.oneyoung.App
# 或者运行
java -cp "target/classes:$CLASS_PATH" com.example.oneyoung.App
2.maven-jar-plugin
使用 maven-jar-plugin
插件,默认的打包方式,用来打普通的 project JAR
包。
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.oneyoung.App</mainClass> <!-- ⚠️ 换成你的主类路径 -->
<addClasspath>true</addClasspath>
<!-- 此处声明lib/ 然后需要在jar的同级目录下创建lib目录,不过我们可以借助maven-dependency-plugin copy-dependencies插件来实现自动化 -->
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.basedir}/target/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
maven-dependency-plugin
的 copy-dependencies
插件,会将项目依赖的 jar
包拷贝到 ${project.basedir}/target/lib
目录下。
然后执行 mvn clean package
以及 java -jar target/my-app-1.0.0.jar
,会发现已经能够正常运行了。
https://maven.apache.org/shared/maven-archiver/examples/classpath.html
3.maven-shade-plugin
maven-shade-plugin
能够直接将所有依赖打包到一个 jar
包内,这种方式不用再考虑 classpath
,但是需要注意,这种方式生成的 jar
包会比较大。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>bundle</shadedClassifierName> <!-- Any name that makes sense -->
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.oneyoung.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
mvn clean package
执行后,会打包一个 my-app-1.0.0-bundle.jar
,然后可以直接 java -jar target/my-app-1.0.0-bundle.jar
。
4.maven-assembly-plugin
maven-assembly-plugin
能够创建自定义的分发包,可以包含任何类型的文件,如 jar
、zip
、tar
等。
另外 assembly
的意思是编排,它允许我们定义一个或多个 assembly
,然后通过 maven-assembly-plugin
将这些 assembly
打包成一个分发包。
如果项目比较简单,可以直接使用内置的 jar-with-dependencies
描述符,它会将所有依赖的 jar
打包进 jar
包中。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.example.oneyoung.App</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
如果我们想要最终的打包如下:
target/my-app-distribution.zip
└── my-app/
├── bin/start.sh
├── lib/your-app.jar + 所有依赖
└── config/application.yml
那么我们可以添加一个 src/assembly/my-zip.xml
:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3
https://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>distribution</id>
<formats>
<format>zip</format>
</formats>
<baseDirectory>my-app</baseDirectory>
<!-- 把主项目 JAR 放到 lib 目录 -->
<fileSets>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>lib</outputDirectory>
<includes>
<include>${project.build.finalName}.jar</include>
</includes>
</fileSet>
<!-- 把资源文件放进 config 目录 -->
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>config</outputDirectory>
<includes>
<include>*.yml</include>
</includes>
</fileSet>
<!-- 可选:加入启动脚本 -->
<fileSet>
<directory>src/assembly/bin</directory>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
</fileSet>
</fileSets>
<!-- 复制依赖 JAR 到 lib/ -->
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<unpack>false</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>
然后改变下 maven-assembly-plugin
的 descriptor
配置:
<configuration>
<descriptors>
<descriptor>src/assembly/my-zip.xml</descriptor>
</descriptors>
</configuration>
执行 mvn clean package
后,会生成 my-app-1.0.0-distribution.zip
包,解压之后,可以执行 java -cp "lib/*" com.example.oneyoung.App
。