Java打包Jar案例深度实战,告别“ClassNotFound”噩梦
目录导读
- 为什么你写的Java程序总是“跑不起来”?——Jar包的本质与价值
- 主流打包三板斧:IDEA、Maven、命令行全案例拆解
- 可执行Jar与普通依赖Jar:别再把“胖Jar”和“瘦Jar”混为一谈
- 高频报错“No main manifest attribute”的根因与三种解法
- 实战问答:打包后资源文件丢失、依赖冲突怎么办?
- SEO精华总结:3个你必须收藏的打包暴力技巧
为什么你写的Java程序总是“跑不起来”?——Jar包的本质与价值
很多刚入行的开发者都有过这样的经历:在IDE里点“运行”一切正常,但一旦把项目发给别人,或者部署到服务器,立刻报错java.lang.ClassNotFoundException。核心原因是你没有把Java项目“打包”成标准分发格式。

Jar(Java Archive)本质上是一个基于ZIP格式的压缩包,里面不仅包含编译后的.class字节码,还包含资源文件(图片、配置、Spring的XML)以及关键的META-INF/MANIFEST.MF清单文件。它承担着“类路径容器”和“程序入口描述符”双重角色。
案例1:最简命令行打包(不依赖任何IDE)
假设你有一个极简HelloWorld项目,目录结构如下:
src/com/example/Main.java
编译并打包的完整命令序列:
# 1. 编译(-d 指定类文件输出目录) javac -d out src/com/example/Main.java # 2. 创建清单文件(手动指定主类) echo "Main-Class: com.example.Main" > manifest.txt # 3. 打包(-cvfm 含义:create verbose file manifest) jar -cvfm myapp.jar manifest.txt -C out . # 4. 运行 java -jar myapp.jar
请注意:如果第2步省略,仅执行jar -cvf myapp.jar -C out .,运行时会报“no main manifest attribute”,这就是80%新手遇到的第一个坑。
主流打包三板斧:IDEA、Maven、命令行全案例拆解
1 IDEA图形化打包(最易上手,适合快速原型)
- 菜单
File -> Project Structure -> Artifacts -> + -> JAR -> From modules with dependencies - 在
Main Class处选择启动类,例如com.example.Main - 关键设置:在
Build on make打钩,确保每次编译自动更新Jar - 最后
Build -> Build Artifacts -> Build
2 Maven打包(企业级标准,强烈推荐)
在pom.xml中加入Spring Boot父依赖或标准maven-jar-plugin,对于非Spring项目,最简配置:
<build>
<finalName>my-maven-app</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
执行mvn clean package,在target/目录下得到my-maven-app.jar。注意:这种方式打包的是“瘦Jar”,不包含第三方依赖库(如gson.jar),如果项目引入了外部依赖,单纯依赖maven-jar-plugin运行时会报NoClassDefFoundError。
3 打“胖Jar”(含依赖)的极简方案
使用maven-assembly-plugin或maven-shade-plugin,区分点:
- Assembly:会把依赖解压后重新打包,可能出现同名文件覆盖冲突。
- Shade:提供
META-INF/services合并功能,处理SPI更安全。
Shade插件核心片段:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</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.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
执行后,target/目录会同时存在原始瘦Jar和original-*.jar(瘦),以及合并后的胖Jar(名称通常带shaded或直接覆盖原Jar名)。
可执行Jar与普通依赖Jar:别再把“胖Jar”和“瘦Jar”混为一谈
- 普通依赖Jar(瘦Jar):只包含业务类,不包含第三方库,它通常作为模块被其他项目
import使用,比如my-utils.jar,体积小,但不可独立运行。 - 可执行Jar(胖Jar):内含所有依赖,并声明
Main-Class,用户直接java -jar app.jar即可启动。Spring Boot默认打出的是可执行胖Jar,其内部目录结构独特(BOOT-INF/lib),不能用普通jar命令解压后直接运行。
实战建议:如果是内部微服务,建议用胖Jar;如果是开源核心库,用瘦Jar。
高频报错“No main manifest attribute”的根因与三种解法
报错信息:java -jar xx.jar后提示no main manifest attribute, in xx.jar。
根因:META-INF/MANIFEST.MF文件中没有Main-Class:行,或者主类类名拼写错误(没包含完整包名)。
解法方案:
- 临时绕过(不推荐):
java -cp my.jar com.example.Main,直接指定主类。 - 重新构建清单:解压Jar,修改
MANIFEST.MF后重新打包,命令:jar uf my.jar META-INF/MANIFEST.MF
- 规范操作:在Maven的
pom.xml中明确指定mainClass(参考上文),确保每次构建一致。
实战问答:打包后资源文件丢失、依赖冲突怎么办?
Q1:我把config.properties放在src/main/resources下,打包进Jar后,运行时报FileNotFoundException,这是为什么?
答:因为FileInputStream("config.properties")默认读取的是当前工作目录(启动Jar时的终端目录),而非Jar内部路径,正确做法是用类加载器读取:
InputStream in = MyClass.class.getClassLoader().getResourceAsStream("config.properties");
注意:Spring Boot中通常用classpath:config.properties解决。
Q2:两个依赖库都包含META-INF/services/xxx文件,导致SPI找不到实现类。
答:这是典型的“依赖合并冲突”。首选方案:使用maven-shade-plugin的ServicesResourceTransformer,配置如下:
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
它会自动合并多个services目录下的同名文件,切忌手动复制粘连文件。
Q3:如何查看Jar包内是否包含某个特定类?
答:jar tf my.jar | grep "ClassName",既可以列出内部所有文件,如果是胖Jar,用jar tf会输出BOOT-INF/lib/下的嵌套依赖,此时建议用mvn dependency:tree看依赖谱。
SEO精华总结:3个你必须收藏的打包暴力技巧
- 快速定位缺失依赖:遇到
ClassNotFound,先执行jar tf fat.jar | grep "com/example/Util",确认是类缺失还是依赖冲突。 - 一步打包并查看体积:
mvn clean package && ls -lh target/*.jar,如果胖Jar只有几十KB,说明依赖没打进去,立即检查shade/assembly配置。 - 终极免安装运行:如果目标机器没有JRE,可以考虑使用
jlink(Java 9+)生成自定义运行时镜像,配合Jar形成绿色软件包,不过对大多数服务器场景,java -jar已经足够。
最后一句忠告:打包不是“点个按钮”那么简单,理解MANIFEST.MF的作用,掌握Maven的插件生命周期,才能在线上环境游刃有余,下次被问“你会打Jar包吗”,你可以把本文案例直接背出来。