Spring Boot 项目如何整合 Ant 完成构建

来源:程序开发作者:追梦人头衔:草根站长
导读:本期聚焦于追梦人创作的《Spring Boot 项目如何整合 Ant 完成构建》,敬请观看详情。为什么一个已经习惯 Maven 或 Gradle 的 Spring Boot 项目还要回头用 Ant?在一些政务内网、金融专网和老旧构建机上,Maven 中央仓库访问受限,Ant 加 Ivy 反而更容易离线落地。Spring Boot 官方并未完全封死 Ant 路线,只要理解可执行 jar 的目录结构,就能用 Ant 完成编译、依赖解析、资源拷贝和 fat jar 打包。本文从 Boot 可执行 jar 的 BOOT-INF 结构讲起,给出 build.xml 关键任务,并说明如何通过 Ivy 拉取依赖、如何将 spring-boot-loader 解包到 jar 根目录、如何在 MANIFEST.MF 中声明 JarLauncher 与 Start-Class。最后还会提到一个常见的打包成功但启动失败的问题。掌握这套方法后,即使没有 Maven 和 Gradle,也能让 Spring Boot 应用稳定产出可运行 jar。

Spring Boot 的默认构建工具集中在 Maven 和 Gradle,但在一些政务内网、金融专网或老旧的持续集成环境中,构建机只允许安装 Ant,且无法随意访问 Maven 中央仓库。此时完全可以借助 Ant 加 Apache Ivy 完成构建。这里所说的“整合 Ant”并不是让 Spring Boot 放弃现有的目录结构或依赖声明,而是通过 Ant 任务完成编译、资源拷贝、依赖解析和可执行 jar 打包,最终产物与 Maven 打出的 fat jar 保持一致。

Spring Boot 项目如何整合 Ant 完成构建

一、Spring Boot 可执行 jar 与普通 jar 的区别

普通 Java 项目的 jar 包通常只包含业务类,依赖要么通过 Class-Path 指向外部 jar,要么完全依赖运行环境提供。而 Spring Boot 的可执行 jar 会把所有依赖都打进去,形成一个可以直接通过 java -jar 启动的 fat jar。它的入口类并不是我们写的 DemoApplication,而是 Spring Boot Loader 提供的 JarLauncher,真正的业务主类则通过 Start-Class 属性指定。

要理解 Ant 打包逻辑,必须先弄清楚 fat jar 的内部结构。一个典型的 Spring Boot 可执行 jar 展开后大致如下:

spring-boot-app.jar
├─ META-INF/MANIFEST.MF
├─ BOOT-INF/classes/          # 应用自己的 class 和资源
├─ BOOT-INF/lib/              # 所有第三方依赖 jar
└─ org/springframework/boot/loader/   # Spring Boot Loader 的 class

这里最关键的目录是 BOOT-INF/classes 和 BOOT-INF/lib。JarLauncher 启动后会创建 LaunchedURLClassLoader,从 BOOT-INF/classes 加载业务类,从 BOOT-INF/lib 加载嵌套 jar。因此 Ant 打包时必须同时满足三个条件:把应用 class 放到 BOOT-INF/classes、把依赖 jar 原样放入 BOOT-INF/lib、把 Spring Boot Loader 的 class 解包到 jar 根目录。

清单文件也需要专门配置,不能只写 Main-Class。一个可用的 MANIFEST.MF 内容如下:

Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.demo.DemoApplication

其中 Main-Class 固定指向 JarLauncher,Start-Class 才是我们的业务入口类。如果只配置了 Main-Class 而没有 Start-Class,启动时会报错,因为加载器不知道要从哪个类开始运行。

二、基于 Ivy 解析依赖并编译 Spring Boot 代码

Ant 本身没有依赖管理能力,如果只靠手工下载 jar 并放进 lib 目录,维护起来会非常痛苦。推荐在 Ant 中集成 Apache Ivy,由 Ivy 负责解析第三方依赖及其传递依赖。Ivy 可以从 Maven 仓库下载 jar,也可以配置成本地仓库或共享盘,适合内网环境。

先在项目根目录创建 ivy.xml,声明 Spring Boot 相关依赖。这里要特别注意,spring-boot-loader 通常不会作为 spring-boot-starter-web 的传递依赖出现,需要单独声明。

<ivy-module version="2.0">
    <info organisation="com.example" module="demo"/>
    <dependencies>
        <dependency org="org.springframework.boot" name="spring-boot-starter-web" rev="2.7.18"/>
        <dependency org="org.springframework.boot" name="spring-boot-loader" rev="2.7.18"/>
    </dependencies>
</ivy-module>

接着在 build.xml 中引入 Ivy 任务,并编写依赖解析与编译目标。下面是一个基础版本:

<project name="spring-boot-ant" default="package" xmlns:ivy="antlib:org.apache.ivy.ant">
    <property name="src.dir" value="src/main/java"/>
    <property name="res.dir" value="src/main/resources"/>
    <property name="lib.dir" value="build/lib"/>
    <property name="classes.dir" value="build/classes"/>
    <property name="dist.dir" value="dist"/>

    <target name="resolve" description="解析依赖">
        <ivy:retrieve pattern="${lib.dir}/[artifact]-[revision].[ext]" sync="true"/>
    </target>

    <target name="compile" depends="resolve">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}" includeantruntime="false" encoding="UTF-8">
            <classpath>
                <fileset dir="${lib.dir}" includes="*.jar"/>
            </classpath>
        </javac>
        <copy todir="${classes.dir}">
            <fileset dir="${res.dir}"/>
        </copy>
    </target>
</project>

如果构建机完全离线,也可以不用 Ivy,改为把所有依赖 jar 提前放入一个固定目录,比如 D:\workspace\spring-boot-ant\lib,然后让 <javac> 直接使用该目录。这种方式虽然直接,但需要保证所有运行时级传递依赖都齐全,否则打包后启动时会缺类。Ivy 的优势在于能自动解决传递关系,因此只要网络条件允许,优先推荐 Ivy 方案。

三、生成可执行 Fat Jar 的核心任务

完成编译后,下一步是最关键的打包环节。Ant 的 <jar> 任务可以把多个目录合并到一个 jar 中,并通过 prefix 属性控制目标路径。我们需要把应用 class 放入 BOOT-INF/classes,把依赖 jar 放入 BOOT-INF/lib,同时把 spring-boot-loader 解包后的 class 放到 jar 根目录。

首先增加一个解包 Loader 的目标。假设 Ivy 已经把 spring-boot-loader-2.7.18.jar 下载到 build/lib,解包时不能把它继续留在 BOOT-INF/lib 中,否则 JarLauncher 无法作为主类被找到。

<target name="prepare-loader" depends="resolve">
    <mkdir dir="${build.dir}/loader"/>
    <unzip dest="${build.dir}/loader" src="${lib.dir}/spring-boot-loader-2.7.18.jar"/>
</target>

<target name="package" depends="compile, prepare-loader">
    <mkdir dir="${dist.dir}"/>
    <manifest file="${build.dir}/MANIFEST.MF">
        <attribute name="Main-Class" value="org.springframework.boot.loader.JarLauncher"/>
        <attribute name="Start-Class" value="com.example.demo.DemoApplication"/>
    </manifest>
    <jar destfile="${dist.dir}/demo-app.jar" manifest="${build.dir}/MANIFEST.MF">
        <fileset dir="${classes.dir}" prefix="BOOT-INF/classes"/>
        <fileset dir="${lib.dir}" prefix="BOOT-INF/lib">
            <exclude name="spring-boot-loader-*.jar"/>
        </fileset>
        <fileset dir="${build.dir}/loader"/>
    </jar>
</target>

这个目标的执行顺序很重要:resolve 先拉取依赖,compile 编译代码并拷贝资源,prepare-loader 解包 Loader,最后由 package 统一合并成 fat jar。打包完成后,可以继续添加一个运行目标,方便本地验证。

<target name="run" depends="package">
    <java jar="${dist.dir}/demo-app.jar" fork="true"/>
</target>

执行时只需要输入 ant package 或 ant run。如果 ANT_HOME 和 JAVA_HOME 配置正常,打包任务会一次性完成。对于 Windows 用户,可以把命令写成:

cd D:\workspace\spring-boot-ant
ant package
java -jar dist\demo-app.jar

需要注意,这里的 spring-boot-loader 版本必须与 Spring Boot 应用版本保持一致。如果项目使用 Spring Boot 3.x,而 Loader 仍然使用 2.x,启动时可能抛出 NoClassDefFoundError 或 UnsupportedClassVersionError。

四、容易踩到的坑与验证方法

在实际操作中,最常见的问题是打包成功但启动时提示 ClassNotFoundException。这类问题多数不是 Ant 命令本身有误,而是依赖没有完整放进 BOOT-INF/lib。例如某些依赖声明为 provided 或 optional,Ivy 默认可能不会把它们带入运行时目录,但这些类在启动阶段又会被加载。因此建议在 ivy.xml 中统一使用默认的 default 配置,或者显式声明 runtime 范围的依赖。

另一个容易忽视的细节是 Start-Class 的配置。它必须是全限定类名,不能只写类名,也不能写成包路径。通常 Spring Boot 应用主类位于 com.example.demo 包下,例如 com.example.demo.DemoApplication。如果这里写错,JarLauncher 会找不到 main 方法,启动直接失败。

验证 fat jar 结构时,可以使用 JDK 自带的 jar tf 命令查看内容。在 Windows 上可以结合 findstr 过滤,在 Linux 或 macOS 上使用 grep。查看依赖是否完整:

jar tf dist\demo-app.jar | findstr BOOT-INF/lib

如果输出的列表中没有包含某个运行依赖,说明 resolve 阶段遗漏了依赖声明,或者 <fileset> 排除了不该排除的 jar。排查清楚后重新执行 ant clean package,一般就能得到可独立运行的可执行 jar。把目录结构、依赖解析和 Loader 解包这三件事做对之后,Ant 构建 Spring Boot 项目并不会比 Maven 或 Gradle 更难维护。

Spring BootAntIvy修改时间:2026-09-30 07:48:19

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0930/63741.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。