公司内网不能用外网这种事,干过项目的都懂。上个月我在客户机房搭环境,整台机器物理隔离,IDEA的在线模板拉不了、Maven中央仓库连不上、Gradle更是直接歇菜,客户还点名要在IDEA Ultimate里把Spring Boot项目骨架建出来。来回折腾一整天,我把离线创建Spring Boot项目这条路彻底走通了,今天把完整过程整理出来,从环境准备、本地仓库搭建到IDEA实操,全部讲清楚。
这篇内容适合谁看:公司内网隔离、操作间不允许外接网络、或者网络环境差到中央仓库根本拉不动的开发者;也适合需要在多台离线机器上重复搭建Spring Boot开发环境的运维和全栈工程师。读完你不仅能手动搭建出可运行的Spring Boot项目,还能搞清楚离线模式下Maven和IDEA到底是怎么配合的,遇到坑了也知道去哪排查。
1. 离线创建的前置认知与整体思路
1.1 为什么“离线创建”这么让人头疼
先统一一下认知。Spring Boot项目的创建,本质上要做两件事:第一,生成项目骨架(目录结构、pom.xml、启动类);第二,解析依赖(把pom里声明的jar包下载到本地仓库)。这两个步骤默认都依赖网络。
- IDAE新建项目时选Spring Initializr,默认访问
start.spring.io,离线环境下这一步直接卡死。 - Maven解析依赖默认访问中央仓库
repo.maven.apache.org,连不上就报Could not resolve dependencies。 - 就算你手工建好了目录和pom,缺少本地依赖仓库,一刷新项目还是满屏红。
所以离线创建Spring Boot项目的本质,不是"不下载依赖",而是把依赖和模板都在有网环境下提前准备好,然后在离线环境里直接复用。
1.2 三种可行方案与取舍
我实测下来,真正可行的离线上手方案有三种,各有适用场景:
| 方案 | 核心思路 | 适合场景 | 缺点 |
|---|---|---|---|
| 方案A:手动创建Maven项目 | 不用IDEA在线模板,手写pom.xml和启动类 | 单个项目、追求最稳 | 步骤多,需要自己写初始化代码 |
| 方案B:本地跑Spring Initializr服务 | 在离线机器上启动一个本地初始化服务,IDEA连本地URL | 团队多台机器批量建项目 | 需要提前准备Initializr的可执行包 |
| 方案C:有网机器生成后整体拷贝 | 在联网环境生成项目,把整个目录和仓库打包带进内网 | 一次性迁移、不常新建项目 | 改项目名和坐标麻烦 |
本文会把方案A作为主线详细讲,因为它是纯手工、零额外工具、成功率最高的路子,也是最不挑环境的一种。方案B的本地初始化服务放在第4章单独讲,适合项目多、想省事的场景。方案C其实就是A的变体,复制粘贴即可,不单独展开。
1.3 版本匹配是你第一个要避开的坑
离线环境里改版本的成本极高,因为改完版本如果本地仓库没有对应jar,你又没网,项目就彻底起不来了。所以动手前先确认三个版本匹配关系。
Spring Boot 2.7.x要求JDK 8及以上,Spring Boot 3.x则强制要求JDK 17及以上,两者不匹配时Maven编译直接报invalid target release或者类库加载异常。IDEA版本也不能太老,IDEA 2020左右的版本对Spring Boot 3的支持不太好,建议至少用2022.1之后的版本,配合JDK 17时体验才正常。
我这次采用的是Spring Boot 2.7.18 + JDK 8,这个组合是内网环境最稳的搭配,老项目兼容性好,JDK 8也是绝大多数公司还在用的主力版本。如果你需要Spring Boot 3,对应的JDK换成17,pom里的配置我后面会单独说明差异。
2. 本地Maven环境与仓库的准备
离线搭建的前提是有一个完整可用的本地开发环境。很多人一上来就开IDEA建项目,结果报错了才发现JDK和Maven根本没配置对,来回折腾浪费时间。按照下面顺序一次配好。
2.1 JDK安装与环境变量
先确认离线机器上有没有JDK,命令行执行java -version,没有就装一个。内网一般不缺JDK安装包,实在没有就在有网环境下载好对应的Windows/Mac/Linux版本拷进去。
环境变量配置注意三点:
JAVA_HOME指向JDK安装根目录,不要带bin。PATH里添加%JAVA_HOME%\bin(Windows写法)或者$JAVA_HOME/bin(Linux/macOS写法)。- 配置完开新终端窗口验证,旧窗口不会自动生效。
IDEA里不看系统PATH,它用的是Project Structure里的SDK配置。首次打开IDEA时在Project Structure > SDKs里把JDK路径指对,否则后面建的Maven项目识别不了编译环境。
2.2 Maven安装与settings.xml配置
Maven没有安装程序,下载二进制压缩包解压就能用。版本选3.8.x或3.9.x,别选太老的3.5,对Spring Boot的插件兼容性不好。
解压后配置MAVEN_HOME和PATH,然后找到conf/settings.xml,这个文件是Maven的核心配置,离线场景重点关注三个节点:
localRepository:本地仓库路径,默认在用户目录.m2/repository下,建议显式指定到一个好找的目录,比如D:/maven-repo,后续拷贝和备份都方便。mirror:镜像配置。有网环境配阿里云镜像加速,离线环境配不配无所谓,因为离线模式下不会真正去远程仓库下载。但如果你的机器偶尔有网,还是建议把阿里云镜像配好,地址是https://maven.aliyun.com/repository/public。offline:这个标签写在settings.xml里也可以,设置为true后Maven强制离线。但我更推荐通过命令行参数来控制,因为它不够灵活,临时想在线拉个包反而要改文件。
2.3 本地依赖仓库的两种准备方式
这一步是离线创建Spring Boot项目的关键,也是最容易出问题的地方。本地仓库本质是一个按坐标目录结构存放jar包的文件夹,Spring Boot项目的依赖就是从这里解析的。
方式一:在有网机器上执行依赖预下载命令
在有网环境建一个测试Maven项目,跑一次完整构建,然后再执行:
mvn dependency:go-offline这个命令会把pom里所有依赖以及依赖的依赖全部下载到本地仓库。下载完以后,把整个本地仓库目录(默认是~/.m2/repository)压缩拷贝到离线机器,再在settings.xml里指向这个目录。
方式二:直接拷贝同事或备份的完整仓库
如果公司内部有现成的.m2/repository目录,直接整体复制是最快的。但要注意,拷贝的时候确认这个仓库被完整使用过,否则缺某个依赖的版本,离线后照样报错。
我在实际操作中强烈建议:不确定仓库是否完整时,提前把要用的Spring Boot版本对应的starter全部手动下载一遍,用下面的命令逐一下载:
mvn dependency:get -Dartifact=org.springframework.boot:spring-boot-starter-web:2.7.18 mvn dependency:get -Dartifact=org.springframework.boot:spring-boot-starter-parent:2.7.18 -Dtransitive=false mvn dependency:get -Dartifact=org.springframework.boot:spring-boot-maven-plugin:2.7.18 -Dtransitive=falsedependency:get是Maven内置插件,即使没有项目pom也能直接下载指定jar到本地仓库。
2.4 IDEA的Maven配置与离线模式开关
IDEA配置好之后,打开Settings > Build, Execution, Deployment > Build Tools > Maven,把下面三项指对:
Maven home path:选择你解压的Maven目录。User settings file:勾选Override,选择conf/settings.xml。Local repository:确认显示的是你指定的本地仓库路径,没有就手动填。
然后把Runner > VM Options和Importing里的联网检查先不管,直接找到Maven > Importing页面,勾选Offline work下面的选项;IDEA 2022.2之后的版本在Maven主设置页面就有Work offline复选框,勾上即可。
这一步的目的是让IDEA的依赖解析完全走本地仓库,不触发任何远程请求。如果本机偶尔有网,不建议勾选,IDEA会自动判断失败后在本地仓库找已有依赖。
3. 离线创建Spring Boot项目的标准操作流程
所有前置工作做完,下面开始正式创建。整个流程我用IDEA 2023.3版本演示,其他版本菜单名称可能略有不同,但逻辑一致。
3.1 在IDEA里新建一个空Maven项目
打开IDEA,选择File > New > Project,左侧选Maven,这里要特别注意:不要勾选Create from archetype列表里的任何模板。因为archetype模板本身需要联网下载,离线环境下勾了必卡死。就让它生成一个最普通的空白Maven项目。
设置Name、Location,JDK选择前面配好的版本,Packaging选jar。点击Create后,项目会生成一个最基础的目录结构:
springboot-offline-demo ├── src │ ├── main │ │ └── java │ └── test │ └── java └── pom.xml第一次导入时IDEA会提示是否自动导入Maven项目,选择Enable Auto-Import,这样以后pom一改就自动刷新。如果IDEA卡在“Resolving Maven dependencies”界面,多半是网络检测超时,直接等待,离线模式下它最终会走本地仓库完成解析。
3.2 编写pom.xml
这是整个离线创建的核心。IDEA生成的pom只有基本坐标,我们把它替换成完整的Spring Boot配置。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>springboot-offline-demo</artifactId> <version>1.0.0</version> <name>springboot-offline-demo</name> <description>Offline Spring Boot Demo</description> <properties> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>几个关键点:
spring-boot-starter-parent作为父POM,统一管理所有Spring Boot依赖版本,所以子依赖里不需要写版本号。如果没有它,每个starter都要手动写version,非常容易版本冲突。relativePath空标签表示父POM从仓库获取,而不是从本地目录找。spring-boot-starter-web是Web项目最基础的starter,包含内嵌Tomcat、Spring MVC、Jackson等。
如果你是Spring Boot 3.x,把版本改成3.2.5之类,java.version改成17,同时注意Spring Boot 3里javax.servlet变成了jakarta.servlet,如果后面引入其他库要留意坐标变化。
3.3 编写启动类与配置文件
创建主启动类SpringbootOfflineDemoApplication.java,放在src/main/java/com/example/springbootofflinedemo目录下:
package com.example.springbootofflinedemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootOfflineDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootOfflineDemoApplication.class, args); } }@SpringBootApplication是组合注解,包含@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan,作用分别是标记配置类、开启自动装配、扫描当前包及其子包的组件。
创建配置文件src/main/resources/application.yml,设置端口和上下文路径:
server: port: 8080 spring: application: name: offline-demo为了验证项目真的能跑,再加一个简单接口。创建HelloController.java:
package com.example.springbootofflinedemo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "Hello, Spring Boot Offline!"; } }到这里,项目骨架、配置、启动类、接口都已经齐全,可以打包验证了。
3.4 用Maven命令验证离线构建
打开IDEA自带的终端(Terminal),注意确认当前目录是项目根目录,执行:
mvn -o clean package-o参数是--offline的简写,强制Maven离线模式。如果本地仓库里依赖齐全,命令会顺利执行,最后输出BUILD SUCCESS,target目录下生成可执行的jar包:
target/springboot-offline-demo-1.0.0.jar执行启动:
java -jar target/springboot-offline-demo-1.0.0.jar看到Started SpringbootOfflineDemoApplication in x.xxx seconds的日志,再访问http://localhost:8080/hello,返回Hello, Spring Boot Offline!,说明整个离线链路完全打通。
4. 进阶:使用本地Spring Initializr服务创建项目
手动创建虽然稳,但每次新建项目都要写一遍pom,项目一多就很烦。其实还有更接近在线体验的方案,在离线机器上自己跑一个本地的Spring Initializr服务。
4.1 本地Initializr服务的原理
Spring Initializr本质上是一个Web服务,你给它参数,它返回一个zip项目压缩包。IDEA里新建Spring Boot项目时,默认请求start.spring.io,如果我们在本地启动一个同样的服务,把IDEA的地址指向localhost,就能像在线一样勾选依赖生成项目。
实际维护这个服务并不复杂,Spring官方把start.spring.io的源码开源在GitHub,我们用Maven把它打成一个可执行jar,这个jar运行时不依赖外网,因为模板和依赖元数据都是内置的。
4.2 在有网环境准备Initializr包
在一台有网的机器上执行:
git clone https://github.com/spring-io/start.spring.io cd start.spring.io mvn clean package -DskipTests构建完成后,start-site/target目录下会生成一个可执行jar,比如start-site-3.0.0.jar。把这个jar和对应的本地仓库一起拷贝到离线机器。
这个项目构建时需要下载大量依赖,一定要确认本地仓库是完整的,否则离线机器上启动这个jar时找不到类就尴尬了。
4.3 离线机器启动服务并接入IDEA
离线机器上执行:
java -jar start-site-3.0.0.jar --server.port=8081启动成功后,浏览器先验证一下http://localhost:8081,能看到Initializr的页面就说明服务起来了。然后打开IDEA,File > New > Project,选择Spring Initializr,把Server URL从默认的https://start.spring.io改成http://localhost:8081,点击Next后IDEA就会从本地服务拉取可选的依赖列表,勾选Spring Web等依赖,生成项目。
这里要强调一点:Initializr生成的只是项目模板,创建完项目后,IDEA解析pom里的依赖仍然走本地Maven仓库。所以第2章的本地仓库准备工作一个都不能少,本地Initializr服务解决的是“模板生成”这一步,而不是“依赖下载”。
4.4 本地Initializr的高级配置
如果公司内网有Nexus或者Artifactory私服,Initializr也可以配置成从私服拉取依赖版本元数据。在启动命令里加参数:
java -jar start-site-3.0.0.jar --spring.initializr.dependencies[0].groupId=com.example更细的配置建议参考start.spring.io项目的application.yml,它在jar包的BOOT-INF/classes目录下,可以用--spring.config.additional-location参数指定外部配置文件覆盖默认值。
5. 常见问题排查与避坑指南
离线环境问题排查比在线环境难得多,因为报错信息往往不直观,而且没有外网可以搜。我把这一路踩过的坑全部整理出来,基本上能覆盖90%的场景。
5.1 依赖无法解析问题速查表
| 现象 | 原因 | 排查方法 |
|---|---|---|
IDEA红字提示Cannot resolve symbol SpringApplication | 本地仓库缺少对应jar | 确认pom里版本和本地仓库路径,用mvn -o dependency:list查看已解析依赖 |
执行mvn -o package报Could not resolve dependencies | 某个传递依赖没下载完整 | 用mvn -o dependency:tree看具体缺哪个,到有网环境补齐 |
| 刷新Maven项目一直转圈 | IDEA在尝试远程连接 | 检查是否勾选离线模式,关闭防火墙弹窗或等待超时 |
报Non-resolvable parent POM | 本地仓库缺少parent的pom | 用mvn dependency:get -Dartifact=org.springframework.boot:spring-boot-starter-parent:2.7.18补齐 |
遇到这类问题,我的排查顺序是:先看报错提示的是哪个坐标,再验证本地仓库对应路径下有没有jar和pom文件,最后看文件大小是否为0。不要上来就猜,数据说明一切。
5.2 _remote.repositories 文件引发的离线失败
这个坑我印象特别深。本地仓库明明有这个jar,离线构建却报“找不到依赖”。搞了半天发现是_remote.repositories文件捣的鬼。
Maven在下载依赖时会在仓库目录下生成_remote.repositories文件,记录这个jar是从哪个远程仓库拿来的。文件内容是:
spring-boot-starter-web-2.7.18.jar>aliyun=如果记录的是某个远程仓库ID,而当前环境没配置这个仓库ID,Maven会认为这个jar不可用,重新尝试下载。离线模式下就报错。
解决办法是在有网环境准备仓库的时候,顺手把_remote.repositories文件删掉。在仓库根目录执行(Windows用PowerShell或cmd):
find ~/.m2/repository -name "_remote.repositories" -exec rm -f {} \;删掉之后Maven会把所有jar当作本地安装的包,不再校验远程来源。这个命令在把仓库拷贝到任何离线环境之后都建议跑一遍。
5.3 版本匹配导致的编译和运行异常
离线环境里用户往往只有一个JDK版本,经常出现Spring Boot 3的项目用JDK 8编译的情况。
编译期报错是:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile [ERROR] invalid target release: 17说明pom里指定了17,但当前JDK是8。运行期报错则是UnsupportedClassVersionError,这种更隐蔽,因为编译可能通过了,启动时才崩。
解决方案很简单,确认JDK和Spring Boot版本匹配后,同步修改pom.xml里的java.version和IDEA的Project Structure > Project SDK,两处必须一致。我在第1.3节给的版本对应关系请务必记牢。
5.4 依赖包下载不完整与.lastUpdated文件
有网环境下载依赖时如果网络抖动,Maven会生成一个.lastUpdated后缀的文件,表示下载失败。这个文件是0字节或者只有几行文本,但Maven看到它就会认为这个依赖“试过但没成功”,优先级比重新下载高,导致后续即使有网也一直拉不下来。
在离线场景下,如果本地仓库是拷贝过来的,偶尔也会带上这类残缺文件。解决方式同样是删除:
find ~/.m2/repository -name "*.lastUpdated" -exec rm -f {} \;删除后重新mvn -o clean package,就可以正常解析了。
5.5 IDEA本身的联网检查与更新提示
离线环境打开IDEA,右下角经常会弹一个Cannot connect to https://plugins.jetbrains.com之类的提示,不影响使用但看着烦。
建议到Settings > Appearance & Behavior > System Settings > Updates里,把自动检查更新关闭。另外Settings > Plugins里如果有插件市场加载,也会超时,关掉即可。IDEA本身是正版授权的,关闭这些联网检查完全不影响功能。
还有一个容易被忽略的点:如果IDEA配置过HTTP代理(有的公司内网环境需要),离线后一定要记得在Settings > Appearance & Behavior > System Settings > HTTP Proxy里选择Auto-detect proxy settings或No proxy,否则IDEA的Maven插件会走代理连远程仓库,直接卡死。
写在最后的几点经验
折腾完这一整套,我最大的感受是:离线开发环境的核心不是“离线”,而是“可复现”。只要本地仓库是完整的、版本是固定的,离线开发完全可以做到和在线一样的效率。
所以我现在养成了一个习惯:每个常用Spring Boot版本整理一套对应的本地仓库压缩包,按版本号命名存放,比如repo-springboot-2.7.18.tar.gz、repo-springboot-3.2.5.tar.gz。换机器、换项目,解压后改一下settings.xml的路径就能用,省掉大量重复排查时间。
另外一个实用小技巧:在settings.xml里可以把你整理好的本地仓库位置配成镜像,其他项目或者同事的机器如果想复用这个仓库,直接用同一份settings.xml即可,不用手动拷贝jar。做法是把本地仓库路径指定到共享盘或NAS目录,内网环境下多机共享同一个仓库,依赖问题直接从源头消失。
最后再提醒一句,离线环境下遇到问题,记得先看IDEA右下角的Event Log和Maven工具窗口的报错详情,很多看似玄学的问题,其实都是本地仓库的文件不完整或者版本不匹配导致的,按第5章的表格对着排查,基本都能解决。