Java打包Jar案例

wen java案例 2

Java打包Jar案例深度实战,告别“ClassNotFound”噩梦

目录导读

  1. 为什么你写的Java程序总是“跑不起来”?——Jar包的本质与价值
  2. 主流打包三板斧:IDEA、Maven、命令行全案例拆解
  3. 可执行Jar与普通依赖Jar:别再把“胖Jar”和“瘦Jar”混为一谈
  4. 高频报错“No main manifest attribute”的根因与三种解法
  5. 实战问答:打包后资源文件丢失、依赖冲突怎么办?
  6. SEO精华总结:3个你必须收藏的打包暴力技巧

为什么你写的Java程序总是“跑不起来”?——Jar包的本质与价值

很多刚入行的开发者都有过这样的经历:在IDE里点“运行”一切正常,但一旦把项目发给别人,或者部署到服务器,立刻报错java.lang.ClassNotFoundException核心原因是你没有把Java项目“打包”成标准分发格式。

Java打包Jar案例

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-pluginmaven-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:行,或者主类类名拼写错误(没包含完整包名)。

解法方案

  1. 临时绕过(不推荐):java -cp my.jar com.example.Main,直接指定主类。
  2. 重新构建清单:解压Jar,修改MANIFEST.MF后重新打包,命令:
    jar uf my.jar META-INF/MANIFEST.MF
  3. 规范操作:在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-pluginServicesResourceTransformer,配置如下:

<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个你必须收藏的打包暴力技巧

  1. 快速定位缺失依赖:遇到ClassNotFound,先执行jar tf fat.jar | grep "com/example/Util",确认是类缺失还是依赖冲突。
  2. 一步打包并查看体积mvn clean package && ls -lh target/*.jar,如果胖Jar只有几十KB,说明依赖没打进去,立即检查shade/assembly配置。
  3. 终极免安装运行:如果目标机器没有JRE,可以考虑使用jlink(Java 9+)生成自定义运行时镜像,配合Jar形成绿色软件包,不过对大多数服务器场景,java -jar已经足够。

最后一句忠告:打包不是“点个按钮”那么简单,理解MANIFEST.MF的作用,掌握Maven的插件生命周期,才能在线上环境游刃有余,下次被问“你会打Jar包吗”,你可以把本文案例直接背出来。

抱歉,评论功能暂时关闭!