首页 / 资讯中心 / 文章详情

Spring Boot热部署完整方案:DevTools自动重启+LiveReload浏览器自动刷新

Spring Boot热部署完整方案:DevTools自动重启+LiveReload浏览器自动刷新 ★ FEATURED ARTICLE
我敢说每个写过 Spring Boot 项目的人都经历过这种折磨后端改了一个接口的返回值切回 IDEA 按 CtrlC等程序停掉再重新启动看那一长串启动日志慢慢刷过去。好不容易起来又切回浏览器按 F5页面重新加载一遍回到刚才的调试位置。如果项目模块多、依赖重这“一停一起”可能就要一两分钟。一天反复几十次看着是零碎时间月底一算浪费掉的时间相当可怕。后来我把这套“Spring Boot 热部署 LiveReload 前端自动刷新”方案跑通之后体验完全是两个世界后端保存即重启静态页面改完浏览器自己刷新整个调试节奏非常顺畅基本可以说是一套完整的“自动部署”式开发环境。这篇文章我打算把我的完整配置过程、每步操作的原理、以及这几年踩过的坑都梳理一遍适合正在用 IDEA 写 Spring Boot 项目、又受不了频繁手动重启的开发者参考。不管你是刚接触热部署的新人还是已经用 DevTools 但没接 LiveReload 的老手这篇文章应该都能给到你一些干货。1. 一套完整的热加载方案值得好好设计1.1 开发现场的真实痛点先说痛点。做 Web 项目开发最常见的状态有三种改后端 Java 代码、改前端静态资源、以及前后端一起来回改。改后端代码传统做法是手动重启服务改前端代码传统做法是手动刷新浏览器。单看每一步操作量都不大但它打断的是你的思路。我印象很深有次调一个列表接口的分页逻辑每调一次参数我就要重复“改代码 → 重启 → 等启动 → 刷新页面 → 看结果”这套动作。一旦中途脑子里还在想着分页边界要不要加一启动日志刷过去几十行思路就断了回来还得重新梳理上下文。这种损耗不只是那几十秒而是注意力切换的代价。更麻烦的是前后端联调阶段。后端改了字段名前端页面也要跟着改前端改了样式后端接口又要对一遍。两边都在改的时候如果还靠纯手动“重启 刷新”你很容易搞不清当前页面上看到的到底是旧数据还是新数据。这时候一个能感知代码变化、自动完成重启和刷新的机制就不只是省时间的问题而是直接决定你调试体验的上限。所以在动手配置之前先要达成一个共识我们要的不仅仅是某个工具而是一套组合拳。后端代码自动重载靠 DevTools前端页面自动刷新靠 LiveReload而把它们串联起来的是 IDEA 里两个容易被忽略的开关。1.2 整体方案选型为什么是 DevTools LiveReloadSpring Boot 生态里做热部署主流方案有两个一个是官方自带的 spring-boot-devtools另一个是商业插件 JRebel。JRebel 确实很强改完代码不需要重启类即时重载对大型单体项目尤其友好但它要收费而且引入之后团队里每个人都要配同一套环境。DevTools 就实在多了开源、省事、跟 Spring Boot 项目天然契合对大多数中小型项目来说完全够用。选 LiveReload 而不是更重的前端热更新框架也是同样的考量。很多前端团队已经习惯用 Webpack Dev Server 或者 Vite 的 HMR那种方案支持模块级热替换确实高级。但问题是如果你只是在一个传统的 Spring Boot 项目里写 Thymeleaf 模板、写原生 CSS 和 JS没有必要把整套前端工程化工具链引进来。LiveReload 便宜、小巧它不刷新模块不保留前端状态它的本职工作只有一个检测到静态资源变化通知浏览器重新加载页面。这就够了。从组件归属上看这个方案也足够“正统”。spring-boot-devtools 依赖里自带了一个内置的 LiveReload 服务端默认监听 35729 端口。也就是说你只要引入 DevTools后端自动重启和前端 LiveReload 服务端就都有了不用额外去装一个 Node 服务。浏览器这边装一个 LiveReload 插件点一下连接剩下的全自动。1.3 核心流程拆解谁在监听谁在动把整个工作流拆开看角色很清晰。IDEA 负责两件事一是检测你按的保存操作把源码重新编译成 class 文件二是在应用运行期间也允许自动编译这个默认是关的需要手动从注册表里打开。Spring Boot DevTools 负责监听 classpath 下的文件变化如果变化的内容是 Java 编译产物它会自动重启应用如果变化的内容是静态资源比如 CSS、图片它不重启只是触发内置 LiveReload 服务端。LiveReload 服务端负责向前端发信号。它通过 WebSocket 协议与浏览器连接浏览器端的 LiveReload 插件收到信号后执行刷新动作。整个链路可以概括为保存代码 → IDEA 自动编译 → DevTools 感知变化 → 按资源类型决定“重启”还是“通知” → LiveReload 插件收到消息 → 浏览器刷新。理解这个链路非常重要因为后面无论哪个环节出了问题你都能按图索骥去排查而不是瞎猜。我自己遇到过好几次“代码保存了但浏览器没刷新”的问题最后发现都是环节之间的衔接断掉了要么是 IDEA 没编要么是浏览器插件没连上真正 DevTools 本身出问题的次数反而很少。2. 动手之前的环境准备版本、依赖、浏览器插件2.1 版本选型和兼容性说明先说版本省得你配到一半发现千奇百怪的问题。这套方案对 Spring Boot 2.x 和 3.x 都适用。2.x 时代大家都熟悉3.x 从 2022 年底开始也逐步成为主流spring-boot-devtools 在两个版本里都有对应模块配置方式基本一致。要特别注意的是Spring Boot 3.x 基于 Jakarta EE部分依赖坐标从 javax 换成了 jakarta但这只影响你自己写的代码不妨碍 DevTools 的配置。IDEA 这边2021 版之后的版本内置的 Spring Boot 支持都足够好老版本也能用但建议至少是 2020.3 以上因为新版对 Spring Boot 运行配置、DevTools 状态显示都有优化。Maven 和 Gradle 都行我用 Maven 居多下文以 Maven 为主。JDK 版本只要跟你的 Spring Boot 版本匹配就行没有特殊的额外要求。浏览器方面Chrome、Edge、Firefox 都有 LiveReload 扩展去各自浏览器的扩展商店搜索 LiveReload 就能找到。我这里建议优先选用户数多的那个版本那些看起来像过期插件的最好别装否则连不上还要排查半天。2.2 快速验证你的基础工程里都有什么在配热部署之前你得先有一个能正常启动的 Spring Boot Web 项目。不需要多复杂只要具备 Controller、静态资源目录、模板目录这三样东西就够。很多新手问我说自己配完 DevTools 之后不生效一问项目结构原来他那个是别人给的只带了一个启动类的空壳工程连 Spring Web 依赖都没有。当然跑不起来也谈不上热部署。一个标准的验证工程结构大概是这样demo/ ├── pom.xml ├── src/main/java/com/example/demo/ │ ├── DemoApplication.java │ └── controller/HelloController.java └── src/main/resources/ ├── application.properties ├── static/ │ ├── css/style.css │ └── js/app.js └── templates/index.htmlstatic 目录放静态资源templates 目录放模板页面这是 Spring Boot 的默认约定。DevTools 对这两个目录的处理方式不同后面我会重点讲这里先有个印象就行。2.3 引入 DevTools 依赖的细节在 pom.xml 里加依赖这一步很简单但有两个细节值得注意。第一个是 optional 标签DevTools 官方推荐把它设为 true这样你的项目作为依赖被别的模块引用时DevTools 不会传递到下游模块。第二个是 spring-boot-maven-plugin 的配置如果你用 repackage 打包成可执行 jarDevTools 会被自动排除这是框架设计好的行为不用担心。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-devtools/artifactId optionaltrue/optional /dependency加完依赖之后Maven 会自动下载对应的 jar。你可以到项目的 External Libraries 里确认 spring-boot-devtools 出现了再接着往下走。如果这里都没加载到后面一切免谈。2.4 浏览器插件安装之后的预检查浏览器插件装好之后先不急着一连串配置可以先做一个预检查。随便打开一个网页点击浏览器工具栏上的 LiveReload 插件图标正常情况下图标会从空心变成实心表示它尝试连接页面。但注意这个连接目前是失败的因为本机还没有服务端在 35729 端口监听。你看到的状态是“已启用”但不代表“已连接”只有当你启动了 Spring Boot 项目之后点同一个域名下的页面插件才能真正连上。这里有个容易误解的点LiveReload 插件不是所有页面都去连的它服务的是当前打开的标签页。如果你开了多个页面请确保你点插件图标的那一瞬间当前激活的就是你要刷新的页面。否则你会发现“插件连上了但页面不刷新”其实是连到了别的页面。3. 后端热部署实操让 IDEA 和 DevTools 配合起来3.1 DevTools 的工作机制为什么它比手动重启快要理解后面那些配置的作用必须知道 DevTools 是怎么工作的。它启动应用时实际上会创建两个类加载器一个 base 类加载器加载那些不会频繁变化的第三方依赖另一个 restart 类加载器加载你自己写的类。当你的代码发生变化时DevTools 只重启 restart 类加载器这一部分依赖的 jar 包不重新加载所以启动速度比完全冷启动要快不少。DevTools 的自动重启本质上是“重启应用上下文”不是字节码级别的热替换。这意味着什么意味着你在代码里定义的单例 bean、静态变量、线程池状态重启之后都会被重置。这跟我们后面要讲的某一个坑有关我先记一笔第四节再展开。DevTools 默认会监听 classpath 的变更。IDEA 里你按保存键源码不一定马上被编译成 class 文件这是很多人配完不生效的根源。所以你要做的第一步是把 IDEA 的自动编译打开。3.2 IDEA 两个关键开关Build project automatically 和 automakeIDEA 的自动编译开关藏得很深。第一个是正常的编译设置打开 Settings → Build, Execution, Deployment → Compiler勾选 Build project automatically。这个开关的意思是当你修改代码并保存之后IDEA 会尝试自动编译项目生成新的 class 文件。但光有这个还不够。第二个开关藏在注册表里。在 IDEA 主界面按 CtrlShiftAmacOS 是 CmdShiftA输入 Registry找到 compiler.automake.allow.when.app.running把它勾上。如果不勾这个应用运行期间 IDEA 不允许后台自动编译那你就算前面勾了 Build project automatically运行中的项目也不会收到新 class 文件。注意这两个开关都必须打开。我见过不少人只开了 Build project automatically运行中的应用代码改了却不生效最后查来查去查到这里。配置完成后最好养成顺手按 CtrlF9 手动编译的习惯。这个快捷键会强制触发一次构建在自动编译偶尔延迟的时候手动按一下能立即唤醒 DevTools。3.3 关闭模板缓存改了页面不生效的元凶很多人改了 Thymeleaf 模板页面点浏览器刷新内容纹丝不动第一反应是热部署坏了。其实热部署好着呢是模板引擎自己带了缓存。生产环境为了性能模板缓存很有必要但开发环境开着缓存每次都要重启才生效这跟没热部署一样难受。在 application.properties 里至少要加这几条spring.thymeleaf.cachefalse spring.freemarker.cachefalse server.servlet.session.persistentfalse第一行关掉 Thymeleaf 缓存第二行关掉 FreeMarker 缓存如果你用传统 JSP 或者别的模板类推把缓存关掉。第三行是关于 Session 持久化的开发环境下建议关掉否则重启后 Session 还在某些状态会显得很“灵异”。还要提醒一句关闭模板缓存是针对开发环境的这些配置千万别直接带到生产环境。生产环境模板缓存能显著降低渲染开销谁在线上关掉它谁就要吃性能的红利消失的亏。3.4 资源配置细节static 和 templates 的差异化处理DevTools 对项目不同路径的监控策略不一样。简单来说templates 目录下的模板文件变化DevTools 会触发一次重启因为模板引擎需要重新初始化而 static 目录下的静态资源变化DevTools 默认不触发重启它会直接让 LiveReload 去刷新浏览器。这个设计很聪明。模板改动往往影响的后端渲染逻辑和上下文重启能保证你看到的跟生产环境接近静态资源改动纯粹是前端表现层的没必要重启后端。你要是觉得模板文件一变就重启有点烦也可以把 templates 里的页面挪一部分到 static 下或者用 DevTools 的 exclude 配置去排除不想监控的路径。我有一次排查“改模板不刷新”的问题发现不是缓存也不是热部署而是我手滑把资源放到了 META-INF/resources 下面那个路径理论上也会被扫描但优先级跟 src/main/resources 不一样容易造成混乱。建议普通项目就用默认目录别瞎折腾额外路径。3.5 验证一下后端自动重启是否生效首次配置完成后最好先做一个最小验证。启动 Spring Boot 项目然后去 Controller 里改一行返回的字符串文本保存。观察 IDEA 控制台你应该能看到类似“Restarting”或“Live Reload”的日志信息应用会在几秒内自动重启。再刷新浏览器看到新的文本说明后端这部分已经通了。如果日志里没有任何反应先按一下 CtrlF9 手动编译再观察。如果还没反应检查 3.2 那两个开关以及依赖里是不是真的引进了 spring-boot-devtools。这一步的通畅程度直接影响后面前端 LiveReload 的体验值得反复确认。4. 前端联动LiveReload 自动刷新的正确接法4.1 LiveReload 协议和 DevTools 内置服务端后端自动重启解决的是“改了 Java 代码”的场景但前端还有一个痛点改 CSS、调 JS每次都要手动切到浏览器按 F5。LiveReload 就是来解决这个问题的。它本质上是一个基于 WebSocket 的刷新协议服务端监听文件变化通过 WebSocket 广播一个消息浏览器插件收到消息后执行 reload。spring-boot-devtools 内置了一个 LiveReload 服务端在你启动 Spring Boot 项目时自动开启默认监听 35729 端口。这个服务端和后端自动重启共享同一个 DevTools 类加载器环境也就是说你只要引入了 DevToolsLiveReload 的服务端能力就已经就位了不需要单独配置端口也不需要另外装 Node 包。有一点要说清楚LiveReload 是“整页刷新”不是“局部热替换”。你改了某个 CSS 选择器的颜色它会把页面整体 reload 一下前端变量的状态、表单里填了一半的内容都会丢失。这在调试简单页面时完全能接受但如果你在调一个特别长的表单每改一下样式就整页刷新那确实难受可以考虑临时把插件断开改完后再连上。4.2 连接浏览器插件的标准动作浏览器插件连接这事看着简单很多人却连错。正确的连接流程是先用 IDEA 启动 Spring Boot 项目确认控制台没有明显的启动异常然后打开你的页面地址比如 http://localhost:8080 最后点一下浏览器工具栏里的 LiveReload 图标。重点来了图标亮起不代表一定连上。你需要打开浏览器的开发者工具找到 Network 或者 Console 面板看一下 WebSocket 连接是否建立成功以及 35729 端口的握手是否正常。如果插件装的是那种来路不明的版本可能连握手都发不出去页面毫无反应。提示LiveReload 插件图标在连接成功后一般会变成实心圆点或者加上外圈高亮。但不同版本表现不一最可靠的判断方式还是看 WebSocket 状态。如果你不想依赖浏览器插件也可以在手写的 HTML 页面里通过 script 标签引入服务端的 livereload.js 脚本script srchttp://localhost:35729/livereload.js/script不过这种方式要求你的前端页面是服务端渲染出来的而且页面里得有相对路径能访问到本机。对于纯静态 HTML 页面还是装插件用起来更省心。4.3 前后端分离项目怎么接 LiveReload如果你现在做的是前后端分离项目前端是一个独立的 Vue 或 React 工程本地有自己的 dev server这时 LiveReload 的接法跟传统模板项目不太一样。最直接的做法是前端项目自己配 HMRDevTools 只负责后端接口部分的热重启前端调接口的时候让 IDEA 的 LiveReload 去刷新浏览器前后端各自刷新各自的部分。实际操作时有人会同时开两套自动刷新结果两边抢着控制浏览器刷新时机页面偶尔会连续重载两次。我的建议是同一时刻只保留一条自动刷新链路。如果是你在调后端接口返回结构前端页面用 LiveReload 刷新就行把前端 dev server 的 HMR 暂时关掉等要做前端页面开发时再反过来把前端的 HMR 开起来把 LiveReload 插件断开。另外注意LiveReload 服务端监听的是 Spring Boot 应用的页面。如果前端是独立的 dev server跑在 3000 端口那它跟 8080 端口的 LiveReload 服务端之间如果没有代理关系是连不上的。你需要在前端 dev server 里配置代理把 LiveReload 的 WebSocket 请求转发到 35729 端口或者干脆别混用别让两个机制打架。4.4 接入 LiveReload 之后的日常体验配置好之后开发体验会顺畅很多。改一个按钮的颜色保存 CSS浏览器几乎跳一下就自动刷新了中间不用手动碰一下浏览器。这种感觉有点“魔法”但它其实只是把文件监听、WebSocket 通信、浏览器刷新这三个环节串联起来而已。有个小技巧调试后端接口时可以在 Controller 里故意写一个错误的返回格式然后保存看页面会不会报错。如果报错信息直接出现在浏览器调试面板说明整个链路已经打通如果页面没有任何反应那你就要回头检查 DevTools 监控的路径是不是包含了你改动的位置。这个验证方法比单纯看控制台日志更直观。5. 跑通全流程从零开始配置一套完整闭环5.1 最小可用配置清单为了让整套方案跑通我把所有的关键配置整理成了一张清单。照着做基本能避开 80% 的坑。配置项具体位置作用引入 DevTools 依赖pom.xml提供自动重启和 LiveReload 服务端关闭模板缓存application.properties模板改动即时生效开启自动编译IDEA Settings → Compiler → Build project automatically保存后自动生成新 class开启运行时自动编译IDEA Registry → compiler.automake.allow.when.app.running运行中允许自动编译安装浏览器插件扩展商店安装 LiveReload接收 WebSocket 信号并刷新启动后点击插件图标浏览器地址栏建立 WebSocket 连接这个清单每个项目只需要配一次。新拉下来的工程只要把这几项复用上整套热加载环境就起来了不用反复琢磨。5.2 多端联调分步验证过程配置完之后建议按下面的顺序做一遍完整的联调验证确保不是“看起来配了其实某个环节没生效”。第一步后端修改验证。修改 Controller 里的某个接口返回值保存。观察 IDEA 控制台确认出现自动重启日志。回到浏览器按 F5 不是自动刷新而是确认数据确实变化了。第二步模板修改验证。修改 templates 下的一个 HTML 文件保存。这次不用手动刷新浏览器稍等片刻页面应该自动出现变化。如果没变化检查模板缓存是否关闭、LiveReload 插件是否处于连接状态。第三步静态资源修改验证。修改 static/css 下的一个样式文件保存。浏览器自动刷新后样式立即改变。这一步验证的是 LiveReload 对静态资源的处理路径。第四步同时修改前后端代码。在后端新增一个接口同时在前端页面里调用这个接口保存。你会发现后端先自动重启重启完成后浏览器再自动刷新顺序是对的。这模拟了真实的联调场景。这一套走完整套方案才算真正落地。不要单测一步就觉得万事大吉联调环境下的稳定性才值得信赖。5.3 资源消耗和建议热部署是把双刃剑方便的同时也有代价。DevTools 会额外占用内存和 CPU 资源因为它维护了双类加载器结构和文件监听。如果你电脑本身配置不高或者项目超级大频繁自动编译和重启会拖慢系统整体响应这时候可以考虑只开 LiveReload关掉后端自动重启。关闭后端自动重启可以在 application.properties 里配置spring.devtools.restart.enabledfalse这样 DevTools 不会自动重启后端但 LiveReload 服务端仍然可能工作。你也可以通过类路径直接排除掉 DevTools等需要时再引入显然那个做法更彻底。生产环境打包时DevTools 本身也会被 maven 插件自动排除这个之前说过不要再重复操心。开发完一个阶段准备要提测或者打包验证时我一般会把 IDEA 的自动编译开关临时关掉改用 CtrlF9 手动编译。这样才能保证打包出的内容是经过充分编译的而不是 IDE 在后台偷偷改过的版本。这个小习惯能避免一些隐蔽的环境差异问题。5.4 在这个基础上还能扩展什么这套方案跑通后你还可以继续扩展。比如在后端增加一个文件监听实现前端构建产物的自动复制或者结合远程调试让本地热部署和远端服务联动。不过我要提醒一句工具链越复杂排障成本越高能简单就简单。我一直觉得开发环境的搭建要稳不要一味追求炫酷。能把“改完代码立刻看到效果”这件事做好对开发效率的提升已经很可观了。6. 高频踩坑实录这些问题我都替你排过雷6.1 后端代码保存了却不自动重启这是最常见的坑。按了 CtrlS控制台毫无反应代码不重编也不重启。排查思路从下往上走先看依赖有没有引入再看 IDEA 两个开关是否都打开最后按 CtrlF9 手动触发一次看日志有没有反应。还有一种情况容易被忽略你改了代码但 IDEA 的自动编译把项目编译到 target/classes 时失败了控制台一团红DevTools 自然感知不到新文件。这种时候先看编译错误把语法问题解决再谈热部署。我曾经在一个大改的文件里漏写了一个分号结果忙活了半天以为是热部署配置失效了。6.2 LiveReload 插件一直连不上连不上的原因有两类一类是服务端确实没起来。检查 Spring Boot 启动日志里有没有 LiveReload 端口相关输出如果有监听失败之类的异常多半 35729 被占用了。另一类是浏览器插件本身的问题特别是从非官方来源装的旧版本插件WebSocket 协议都跟现在的浏览器不兼容。遇到这种情况先把 35729 端口的情况看一遍。Windows 上可以用命令查看端口占用macOS/Linux 可以用 lsof 查看。如果端口被别的程序占了要么杀掉占用进程要么调整 DevTools 里的 liveReload 端口配置。不过说实话我遇到更多的还是插件版本问题换成官方商店里的新版本基本就解决了。6.3 页面内容不更新模板缓存还是浏览器缓存改完模板浏览器刷新之后还是老样子这是第二个高发坑。先确认 application.properties 里是否设置了 thymeleaf.cachefalse。如果设置了那再看浏览器开发者工具里的 Network 面板确认 HTML 请求是否真的返回了新的内容。很多时候后端其实已经返回新页面了但浏览器缓存了旧资源导致你看到的是旧内容。浏览器缓存这个事在静态资源上表现尤其明显。你可以给静态资源加版本号参数或者用 DevTools 面板里勾选 Disable cache 选项。调试 AJAX 请求时那个 Network 面板的缓存禁用开关会帮你省掉不少迷惑。6.4 热部署导致的环境状态问题前面提到过 DevTools 自动重启会重置 Spring 应用上下文有一些状态依赖持久化的场景会出问题。比如你在内存缓存里存了一些登录用户信息重启之后缓存没了用户又要重新登录又比如 WebSocket 长连接、定时任务调度器重启之后这些状态都会丢失。规避办法是分场景处理。如果只是开发环境调试丢失就丢失重新登录一次就行别太纠结。但如果你在调试一个跟外部系统交互的长事务流程每次重启都要重放请求那就要认真考虑把状态外置到 Redis 或数据库里而不是塞在 JVM 内存中。热部署不会改掉代码本身的错误只会把环境问题暴露得更早。6.5 自动编译与构建工具冲突IDEA 的自动编译和 Maven 的自动构建如果同时启用有时会出现重复编译、互相干扰的情况。我见过同事的 IDEA 里开了 Maven 的自动导入、自动构建同时 Spring Boot 插件又在监听 target/classes结果一次代码保存触发了两次编译两次重启页面来回跳。这种问题没有太通用的完美解各人习惯不同。我的建议是明确主次开发调试时让 IDEA 的自动编译来驱动热部署Maven 只在需要处理依赖变更时手动触发。两个工具不要同时去抢同一个 target/classes 目录的写操作避免文件处于半写入状态。7. 最后分享一点我的真实体会这套“IDEA 自动编译 Spring Boot DevTools 自动重启 LiveReload 前端刷新”的组合我从一开始零散地试过各个组件到后来终于把整条链路串起来前后也折腾了不少时间。刚开始总觉得某一步是多余的后来才发现每个开关都有它存在的意义。IDE 不自动编后端就感知不到后端不感知LiveReload 就没有信号源浏览器不连插件信号灵也不显灵。这是一个闭环。我个人现在的习惯是每天开工先启动项目确认控制台打印出 LiveReload 服务端在 35729 端口的监听提示然后打开浏览器点一下插件图标接着才开始写代码。这套动作已经变成了肌肉记忆省下来的不只是按 Ctrl C、F5 的次数更是每次切换上下文时那种精神上的重新装载。如果你在配置时被某个细节卡住了多半不是方案有问题而是某个开关没到位。从依赖、IDE 开关、缓存配置、浏览器插件这四个方面逐一排查绝大部分问题都能解决。等你也能够“保存即生效”地开发一整天你会明白我为什么这么推崇这套方案。
阅读完成 · 觉得有帮助?
咨询建站