Skip to content

Maven 提供了多种打包方式,其中常见的包括三种:jarshadeassembly。下面是它们的详细比较:

  1. Jar 打包方式:
  • 描述:这是最常见的打包方式,它创建一个标准的 Java JAR 文件。
  • 优点:简单直接,适用于大多数简单项目。
  • 缺点:不能包含项目的依赖,如果项目有外部依赖,用户必须手动将它们添加到类路径中。
  1. Shade 打包方式:
  • 描述:Maven Shade 插件允许创建一个可执行的 JAR 文件,其中包含所有依赖。
  • 优点: 生成一个独立的可执行 JAR,无需用户手动添加依赖。
  • 缺点: 可能会导致 JAR 文件较大,不适合所有项目。
  1. 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 中)。

这一节,我们先说明一个较为笨的方式,后续章节,会通过介绍工程化插件的方式,解决这个问题。

shell
# 获取 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 包。

xml
<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>
xml
<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-plugincopy-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 包会比较大。

xml
<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 能够创建自定义的分发包,可以包含任何类型的文件,如 jarziptar 等。

另外 assembly 的意思是编排,它允许我们定义一个或多个 assembly,然后通过 maven-assembly-plugin 将这些 assembly 打包成一个分发包。

如果项目比较简单,可以直接使用内置的 jar-with-dependencies 描述符,它会将所有依赖的 jar 打包进 jar 包中。

xml
<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>

如果我们想要最终的打包如下:

plaintext
target/my-app-distribution.zip
└── my-app/
    ├── bin/start.sh
    ├── lib/your-app.jar + 所有依赖
    └── config/application.yml

那么我们可以添加一个 src/assembly/my-zip.xml

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-plugindescriptor 配置:

xml
<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