
本文详解如何在不依赖 IDE 的纯命令行环境下,正确运行含依赖的 Maven 项目,涵盖 mvn exec:java、mvn dependency:copy-dependencies、可执行 Fat JAR 打包等核心方案,并给出 Docker 构建中的标准化实践。
本文详解如何在不依赖 ide 的纯命令行环境下,正确运行含依赖的 maven 项目,涵盖 `mvn exec:java`、`mvn dependency:copy-dependencies`、可执行 fat jar 打包等核心方案,并给出 docker 构建中的标准化实践。
在 Maven 项目中,直接通过 java -cp target/xxx.jar MainClass 运行程序却遭遇 NoClassDefFoundError,本质是手动绕过了 Maven 的依赖解析机制——.m2/repository 是一个结构化仓库(按 groupId/artifactId/version/ 分层存储),而非扁平化的 JAR 集合,因此无法用通配符(如 -cp ~/.m2/repository/**/*jar)或简单目录路径加载全部依赖。正确的做法是让 Maven 主动生成并管理运行时类路径,而非人工拼接。
✅ 推荐方案一:使用 exec-maven-plugin 直接运行(开发调试首选)
在 pom.xml 中配置插件,声明主类:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<mainClass>com.example.App</mainClass>
<!-- 可选:传递 JVM 参数 -->
<systemProperties>
<systemProperty>
<key>log.level</key>
<value>DEBUG</value>
</systemProperty>
</systemProperties>
</configuration>
</plugin>
</plugins>
</build>执行命令即可一键运行(自动包含所有 compile 范围依赖):
mvn compile exec:java
✅ 优势:零配置 CLASSPATH,支持热重载调试(配合
exec:java -Dexec.args="-agentlib:jdwp..."),适用于本地快速验证。
✅ 推荐方案二:生成完整类路径字符串(轻量脚本化场景)
mvn dependency:build-classpath 确实是官方支持的方案,但无需手动拼接——可结合 shell 命令自动完成:
# Linux/macOS java -cp "$(mvn dependency:build-classpath -Dmdep.outputFile=/dev/stdout | tr '\n' ':')$(pwd)/target/my-app-1.0-SNAPSHOT.jar" com.example.App # 或更清晰的两步法(推荐) CP=$(mvn dependency:build-classpath -Dmdep.outputFile=/dev/stdout | tr '\n' ':') java -cp "$CP:$(pwd)/target/my-app-1.0-SNAPSHOT.jar" com.example.App
⚠️ 注意事项:
- Windows 用户需用
mvn dependency:build-classpath -Dmdep.outputFile=cp.txt && set /p CP=<cp.txt java com.example.app></cp.txt> - 此方式生成的 classpath 仅包含
compile和runtime范围依赖(默认排除test和provided),符合生产运行语义。
✅ 推荐方案三:构建可执行 Fat JAR(Docker / 发布部署首选)
对 Docker 或分发场景,应打包为自包含的可执行 JAR(含所有依赖 + 正确 MANIFEST.MF)。推荐使用 maven-shade-plugin(比 assembly 更成熟,支持类重定位防冲突):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.App</mainClass>
</transformer>
</transformers>
<!-- 可选:解决 META-INF 文件冲突 -->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>构建后执行:
mvn clean package java -jar target/my-app-1.0-SNAPSHOT.jar # 无需额外 classpath
? Docker 构建最佳实践
Dockerfile 应遵循 “构建阶段分离”原则,避免将 .m2 仓库打入镜像:
# 构建阶段:利用 Maven 缓存加速 FROM maven:3.9.16-openjdk-17-slim AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B # 预下载依赖(提升后续缓存命中率) COPY src ./src RUN mvn clean package -DskipTests # 运行阶段:极简基础镜像 FROM openjdk:17-jre-slim WORKDIR /app COPY --from=build /app/target/my-app-1.0-SNAPSHOT.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"]
✅ 关键点:
- 使用多阶段构建,最终镜像不含 Maven、源码、编译工具链;
mvn dependency:go-offline提前拉取依赖,使pom.xml变更时仅重建该层;- 不挂载
.m2卷到容器——这会破坏构建可重现性。
❌ 不推荐的做法(避坑提醒)
-
<scope>system</scope>引入本地 JAR:破坏可移植性,CI/CD 失败,Maven 3.9+ 已标记为过时; -
手动
export CLASSPATH=...指向.m2/repository:路径层级嵌套,无法匹配坐标依赖关系,必然失败; -
在
settings.xml中修改<localrepository></localrepository>后期望 JVM 自动识别:JVM 完全不读取 Maven 配置,此路径仅对 Maven 进程生效。
总结:Maven 的设计哲学是“约定优于配置”。与其对抗其仓库模型,不如用 exec:java 调试、用 shade 打包、用多阶段 Dockerfile 发布——每一步都由 Maven 原生能力保障一致性与可重现性。


















