ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot全链路开发指南:从零搭建到部署的完整实战

Spring Boot全链路开发指南:从零搭建到部署的完整实战 说实话我在后台收到的私信里被问得最多的问题之一就是“我想学编程但看到那些教程上来就讲什么 JVM 调优、高并发架构直接劝退了该怎么办” 所以我一直想做一份真正面向小白的、能把开发流程从头到尾串起来的 Spring Boot 学习内容。这个框架是目前 Java 后端开发事实上的“标配”学它不是因为它是银弹而是因为市面上的公司尤其是中小型团队新项目基本都在用 Spring Boot 构建接口服务。这篇全链路指南就是按照我实际做项目的顺序来写的从零开始做完一个能跑通前后端的小系统理解“接口开发”这件事到底是怎么回事。这篇内容适合完全没接触过 Java 或者只是学过语法、但不知道接下来怎么用的人也适合那些做过几个后端小工具、但一直没系统走完一个 Web 项目流程的朋友。读完你会对 Spring Boot 的常用组件、分层方式、请求处理流程、数据库交互以及异步操作有清晰的第一印象。这个“全链路”并不是让你学成专家而是帮你建立起从代码到运行的整体认知框架之后你再看任何架构图、中间件文档都不会再有那种云里雾里的感觉。1. 先说清楚Spring Boot 到底解决了什么问题1.1 一个朴素的问题Web 后端到底在做什么你打开任何一个 App 或者网站看到的内容都不是凭空出现的。比如你打开外卖软件首页店铺列表、菜品价格、你的历史订单这些数据都存放在某个服务器上。这个“服务器上运行的程序”就是后端它的核心工作说穿了只有三件事接收前端发来的请求比如“把 id 为 3 的商品信息给我”、处理业务逻辑比如判断你是否有权限查看这个商品、返回结果把商品数据包装成固定格式的 JSON 字符串发回去。在没有 Spring Boot 之前Java 写 Web 项目是很狼狈的。你需要手动配置一堆 XML 文件来管理对象之间的依赖关系要自己找 Servlet 容器比如 Tomcat然后把项目打包成 war 包塞进去启动时候还要小心翼翼地调整配置否则就会遇到各种让人头大的类加载问题。Spring Boot 做的最重要的一件事就是把这些繁琐的“环境搭建”和“基础设施”工作自动完成屏蔽掉了大量底层细节。你只需要关注自己业务代码本身剩下的事情框架帮你搞定。1.2 Spring Boot 的三板斧自动配置、起步依赖、Actuator很多人分不清 Spring 和 Spring Boot这里我用一个生活化类比解释。如果 Spring 是一套组合菜刀、砧板、炉灶的标准厨房规范那 Spring Boot 就是你直接拎包入住的精装公寓水电燃气接通了、家具摆好了、连调料都备齐了。你进去只需要炒菜。这里有三件事是 Spring Boot 的核心价值理解它们你就理解了框架存在的基本逻辑自动配置Auto Configuration框架根据你引入的依赖自动把常用的配置项设置好。你引入了spring-boot-starter-web它就默认帮你嵌入一个 Tomcat 服务器并自动配好 Spring MVC 的组件。你不需要写web.xml不需要配置DispatcherServlet它在背后默默地帮你把环境理顺。起步依赖Starter把某个场景需要的所有 jar 包打包在一起。以前你引入一个功能模块要手动拉五六个容易版本冲突的 jar现在只需要引入一个 starter依赖关系框架全处理了。比如你要操作数据库引入spring-boot-starter-data-jpa就够了。Actuator这是 Spring Boot 自带的一个监控模块你可以通过 HTTP 端点查看当前应用的健康状态、缓存信息、线程信息等。对刚入门的小白来说它最大的意义在于当你的应用启动失败时可以从这些端点中快速定位是不是端口被占用、数据库连接池是否建立等基础问题。1.3 “全链路”究竟指的是一条什么样的链路我在标题里写了“全链路”这词这两年确实被说烂了。但具体到这个场景我定义的很清楚从你敲下mvn spring-boot:run命令到浏览器看到一个 JSON 返回这中间代码和数据到底经历了什么。这条链路上涉及 Web 请求处理、业务逻辑封装、数据持久化以及部署运行。换句话说这个“全链路”是从一个最简单的工程跑起来开始逐渐挂上接口、连上数据库、加上统一异常处理和异步任务最终形成一个真正能上线运行的后端服务。整个链路打通之后你对“用 Java 做后端服务”这件事就有一个完整的感性认知之后再去看微服务、容器化部署等概念时会有一个牢固的锚点。2. 零基础起跑环境准备与工程搭建2.1 你需要准备哪些东西以及为什么是它们作为一个新手第一步往往是卡在环境搭建上。很多教程默认你已经装好了 JDK 和 IDE但对小白来说这一步本身就足够劝退。这里我给出一个经过验证的最低配置JDK 17这是当前稳定且广泛使用的主流版本Spring Boot 3.x 要求 JDK 17 起步。如果你学的是老教程Spring Boot 2.x那用 JDK 8 也没问题。但新项目建议直接上 17不要把精力浪费在旧版本的兼容性问题上。Maven 3.8项目构建和依赖管理工具。简单理解你用 Maven 的坐标来下载和管理各种第三方库不用自己去找 jar 包。IntelliJ IDEA Community Edition社区版免费功能足够日常学习。不需要一开始就追求旗舰版。安装的时候注意一个细节JDK 安装完成后务必配置JAVA_HOME环境变量确保在命令行里输入java -version能把版本信息正常打印出来。很多小白安装完 IDEA 发现跑不了项目十有八九就是环境变量没有配或者配错了路径。2.2 用 Spring Initializr 快速生成初始工程不要自己从零建 Maven 项目慢慢添加依赖那是在浪费生命。正确姿势是打开https://start.spring.io/如果访问慢也可以用阿里云镜像的 start 页面选好语言、版本和依赖点击 Generate一个可以直接打开的初始化工程就下载下来了。依赖选择上我建议第一次只勾选这三个Spring Web让项目拥有开发 Web 接口的能力。Spring Boot DevTools开发时修改代码能自动重启省去手动重启的麻烦。Lombok通过注解减少样板代码比如类里面常见的 getter、setter、构造方法用一个Data注解就全自动生成了。工程下载解压之后IDEA 里打开它是一个 Maven 项目首次加载依赖会比较慢。如果你在国内强烈建议把 Maven 的中央仓库地址改成阿里云镜像这个操作能把依赖下载速度提升一个数量级。配置方法是在 Maven 的settings.xml文件里的mirror标签中加入阿里云仓库镜像地址加完之后依赖下载速度真的会让人怀念。2.3 启动第一个 Spring Boot 应用的完整动作当工程里的 Maven 依赖都加载完成后找到application.properties文件或者application.yml这算是项目的核心配置文件。然后直接运行主类中带有SpringBootApplication注解的main方法。看到类似Tomcat started on port(s): 8080的日志说明启动成功。浏览器访问http://localhost:8080如果出现一个带错误信息的 JSON 页面或者空白页都是正常现象因为你还没写任何接口。这里小白最容易慌的一个点老是直觉地认为启动后应该立刻能看到一个页面。但实际上 Spring Boot 本身不生成页面它只是把 Tomcat 服务器拉起来了。没有请求路径时自然 404。确认启动成功用日志信息判断就行不需要看到页面。3. 开发第一个接口从 HTTP 请求到 JSON 响应3.1 用 RestController 写一个能被访问的地址搞定了环境下面进入核心编码环节。打开工程中src/main/java目录下的主包结构新建一个controller包在里面创建一个HelloController类。代码非常简单package com.example.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; RestController public class HelloController { GetMapping(/hello) public String sayHello() { return Hello, Spring Boot!; } }把项目重新启动浏览器访问http://localhost:8080/hello你会发现页面上出现了Hello, Spring Boot!。这就是你亲手写出的第一个接口。这里有两个注解初学者容易混淆。RestController表示这个类中所有方法的返回值都会直接被写入 HTTP 响应体不再经过视图解析器去找一个叫 Hello 的 html 页面。它其实是Controller和ResponseBody的组合。而GetMapping(/hello)可以理解为注册了一个路由规则指定当访客用 GET 方式访问/hello路径时调用下面的方法。3.2 请求参数接收PathVariable 与 RequestParam 的区别一个只会返回固定字符串的接口没有实际价值。在真实的开发中接口需要接收前端传过来的数据。最常见的有两种方式路径参数和查询参数。路径参数就是 URL 中{id}这种写法比如GET /user/123。在 Java 方法中用PathVariable接收GetMapping(/user/{id}) public String getUser(PathVariable Long id) { return 用户ID id; }查询参数就是GET /search?keywordspring这种写法用RequestParam接收GetMapping(/search) public String search(RequestParam String keyword) { return 搜索关键词 keyword; }它们的区别从 URL 上就能直观看出/user/123是在路径里的资源定位适合表达“我要找某个资源”的语义/search?keywordspring是用查询参数来过滤适合表达“我要搜索符合某条件的资源列表”。这个基础认知在实际接口设计调研时非常有用能帮你少走弯路。3.3 返回统一格式的 JSON一个基础的接口设计习惯早期很多新手写接口的时候喜欢“想到哪里写到哪里”有时候返回一个字符串有时候返回一个对象有时候返回一个 Map。这样客户端对接时非常痛苦因为响应结构乱七八糟无法统一解析。实际企业开发中几乎每个团队都会有统一的响应包装类。我习惯性地让项目里所有后端接口都返回一个统一的 JSON 结构大概长这样{ code: 200, message: 操作成功, data: { ... } }对应的 Java 类很简单package com.example.demo.common; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }这样前端拿到响应之后只需要判断code是否为 200 即可不需要为每个接口单独适配数据结构。这个是工程化开发的基础素养我建议你在第一个接口开发时就把这个习惯养成。4. 连接数据库从 JdbcTemplate 到 MyBatis-Plus 的演进4.1 没有数据库的服务器就像一个没有记忆的人我们前面写的接口所有数据都在内存里一旦进程重启数据就丢了。现实中这当然不行用户注册的账号、发布的文章、商品的库存都需要持久化。数据库就是这里的核心依赖。Java 后端项目里最常用的数据库是 MySQL。对于初学者本地安装配置 MySQL 是一件麻烦事。我推荐用 Docker 安装一个 MySQL 8 容器这样不仅能省去本机安装的折腾还能顺便熟悉容器化这个现代开发的基本操作。运行这个命令即可docker run -d \ --name mysql-dev \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEspringboot_demo \ mysql:8.0等这个容器跑起来后你本地的3306端口就有了一个可以直接用的 MySQL 实例数据库名叫springboot_demo用户名 root密码 123456。4.2 用 JdbcTemplate 操作数据库理解最底层的流程在引入 MyBatis-Plus 或 JPA 之前我强烈建议新学者先用 Spring 自带的 JdbcTemplate 写一个简单的增删改查目的是搞清楚“Java 代码到底是怎么操作数据库的”。这个过程会打破很多神秘感。修改pom.xml引入数据库相关依赖spring-boot-starter-jdbc mysql-connector-j然后在配置文件中配置数据源spring.datasource.urljdbc:mysql://localhost:3306/springboot_demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.password123456 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver在 Repository 层用 JdbcTemplate 写查询方法package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import java.sql.ResultSet; import java.sql.SQLException; Repository public class UserRepository { private final JdbcTemplate jdbcTemplate; public UserRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } public User findById(Long id) { String sql SELECT id, name, email FROM user WHERE id ?; return jdbcTemplate.queryForObject(sql, new RowMapperUser() { Override public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user new User(); user.setId(rs.getLong(id)); user.setName(rs.getString(name)); user.setEmail(rs.getString(email)); return user; } }, id); } }这段代码里可以看到一个重要的设计模式JdbcTemplate对象被自动注入到UserRepository中。你没有主动写new JdbcTemplate()这背后的机制就是 Spring 的依赖注入DI。Spring 容器负责对象的创建和生命周期管理别的地方用到同一个对象时直接把引用传给你。这种“你要什么容器给什么”的机制是 Spring 框架最核心的思想也是整个框架的灵魂所在。在这个阶段你可能会发现写一个简单的查询居然要啰嗦这么多行。你会想有没有更简单的方式有。这就是我们下面要说的 MyBatis-Plus。4.3 MyBatis-Plus一个对新手极度友好的数据访问框架MyBatis-Plus简称 MP是国内使用率极高的一款 MyBatis 增强组件。它所谓的“增强”体现在你把方法名定义在接口上就能自动生成大部分的 SQL不需要写繁琐的映射文件。引入依赖dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.7/version /dependency接着创建一个用户实体类和一个 Mapper 接口package com.example.demo.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; Data TableName(user) public class User { TableId(type IdType.AUTO) private Long id; private String name; private String email; }package com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.demo.entity.User; import org.apache.ibatis.annotations.Mapper; Mapper public interface UserMapper extends BaseMapperUser { }注意我们没有写任何实现类。BaseMapperUser这个接口包含了一堆现成的通用方法比如insert、selectById、updateById等。当你在业务代码里调用这些方法时MyBatis-Plus 在运行时动态生成 SQL 并执行。这个体验对新手来说有多舒适比如你要查一个 ID 为 5 的用户简单来一行User user userMapper.selectById(5L);不需要写 SQL不需要关心数据库连接怎么获取、语句怎么预编译、结果怎么映射对象所有事都封装好了。对你来说你只关心“我要查一个用户”至于如何查框架完全代劳了。4.4 事务为什么数据库操作需要 Transactional数据库操作中有一个绕不开的话题就是事务。我给你描述一个经典场景用户在下单的时候系统需要做两步操作。第一步是往订单表插入一条记录第二步是扣减商品库存。如果第一步成功执行了第二步执行时报错比如库存不足那么数据库里就会残留一条严重不合理的订单记录而实际上用户却没有购买成功。事务就是用来解决这类问题的机制把多个数据库操作绑定为一个原子单元要么全部成功要么全部失败回滚。Spring Boot 中启用事务非常简单只需要在方法上加一个注解Transactional public void createOrder(OrderDto dto) { orderMapper.insert(dto); stockMapper.decrease(dto.getProductId(), dto.getCount()); }当方法执行过程中抛出 RuntimeException 时Spring 的 AOP 机制会拦截到异常并自动把数据库状态回滚到方法执行之前。这个注解是实际项目开发中最高频的注解之一。我见过不少刚入职的新人因为没加Transactional导致测试阶段数据错乱排查半天都找不到原因。这个习惯越早养成越好。还有一个细节只有public方法上的Transactional注解才会被 Spring 代理识别。如果你把这个注解加在一个私有方法上它不会生效而且不会有任何报错提示。这种“静默失效”是最容易踩的坑。5. 异步编程让接口更快响应的关键手段5.1 同步 vs 异步你得先知道用户等待时发生了什么继续用外卖场景类比。你在外卖 App 下单支付后端接口马上要做的事情包括确认订单信息、通知商家接单、生成配送单。如果这些操作全部串行执行总共耗时可能是几百毫秒甚至几秒。虽然数字看起来不大但用户点击“确认支付”按钮后转圈圈转几秒才能跳转这个体验是相当糟糕的。更好的方式是把那些并非即时必需做的操作比如通知商家、记录日志、发送通知短信挪到后台异步执行让接口先把“支付成功”的状态立刻返回给用户后台悄悄慢慢处理剩余的事情。这就是异步编程的核心收益提升接口响应速度同时减少用户等待时间。5.2 Spring Boot 中的 Async 注解及它的坑Spring Boot 实现异步非常简单只需要两步。第一步在启动类或配置类上添加EnableAsync注解SpringBootApplication EnableAsync public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }第二步在需要异步执行的方法上添加Async注解package com.example.demo.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; Service public class NotificationService { Async public void sendSms(String phone, String content) { // 模拟耗时操作比如调用短信服务商SDK try { Thread.sleep(3000); System.out.println(已向 phone 发送短信: content); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }当业务接口调用这个sendSms方法时它不会阻塞当前线程而是立即返回真正执行短信发送是在 Spring 的线程池中另起线程执行的。这里有新手最容易犯的经典错误在同一个类内部调用带有 Async 注解的方法异步不会生效。原因是 Spring 的Async基于代理实现同一个类内部的方法调用没有经过代理注解自然就失效了。你写完代码一看为什么没异步就是这个问题。解决方式是异步方法放到另一个类中通过注入外部类调用。5.3 CompletableFuture两个异步任务之间的组合编排Async确实能解决“发送完就走”的需求但如果你的业务逻辑更复杂比如需要并行执行两个耗时任务然后等它们都完成后再把结果合并到一起作为接口的返回值Async就不太方便了因为你在主线程里还要关心它们什么时候完成。这时候可以请出CompletableFuture这个工具。它是 Java 8 引入的增强版Future最大特点是天然支持回调编程可以组合多个异步任务。简单展开一个场景假设一个查用户详情的接口需要同时查用户基础信息和用户的最近订单信息这两个查询独立且耗时较高。package com.example.demo.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.concurrent.CompletableFuture; Service public class UserQueryService { Async public CompletableFutureString queryUserBaseInfo(Long userId) { try { Thread.sleep(1000); return CompletableFuture.completedFuture(用户基础信息); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } Async public CompletableFutureString queryUserOrders(Long userId) { try { Thread.sleep(1500); return CompletableFuture.completedFuture(用户订单列表); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } }在控制器中这样组合CompletableFutureString baseInfoFuture userQueryService.queryUserBaseInfo(userId); CompletableFutureString orderFuture userQueryService.queryUserOrders(userId); // 等待两个异步任务都完成合并结果 CompletableFutureString combinedFuture baseInfoFuture .thenCombine(orderFuture, (baseInfo, orders) - baseInfo orders); String result combinedFuture.join();这里有一个很重要的体验变化在不使用异步时两个串行操作总共耗时约 2.5 秒而使用 CompletableFuture 并行执行时总耗时是 1.5 秒以最慢的任务为准。这种优化效果非常直观。thenCombine是 CompletableFuture 的一个组合方法接收两个异步任务的结果当两者都完成时合并成一个新的结果。如果异步任务内部发生异常join()时会抛出CompletionException需要通过exceptionally()或handle()方法去处理异常否则错误很难被直观发现。6. 接口文档与前端联调别再把时间浪费在口口相传6.1 springfox 3.0.0 与 Spring Boot 2.6 的兼容性之痛写好了后端接口接下来面临的问题就是如何把接口规范告诉前端同学。项目早期不少人习惯于把接口文档发给前端前端再照着写代码。这种沟通方式效率很低接口多了以后文档很快过时前端读到的信息永远是不完整的。为了自动化生成接口文档Swagger 系列工具应运而生。你只需要在接口上写几个注解运行时就能在浏览器里看到一个包含所有接口信息的文档页面前端可以直接阅读并调试。这里有个常见的坑需要特别提醒springfox 3.0.0Swagger 2 的 Spring Boot 集成库与 Spring Boot 2.6 存在兼容性问题。报错信息通常是一堆NPE空指针异常和PathPatternMatcher相关错误。原因是 Spring Boot 2.6 之后默认的路径匹配策略改成了PathPatternParser而 springfox 还在用旧策略。解决办法有几种。最推荐的是不要再用 springfox 了直接换成springdoc-openapi这个新的库它是 OpenAPI 3 规范的官方实现对 Spring Boot 2.6 和 3.x 支持良好。引入dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.6.0/version /dependency如果你因为历史项目原因必须用 springfox可以尝试在配置文件中加上旧路径匹配策略spring.mvc.pathmatch.matching-strategyant_path_matcher实际经验是不要把时间浪费在这个坑上新旧项目都建议直接用 springdoc。6.2 写一套最小可用文档三个注解快速上手springdoc 的注解风格与 Swagger 2 类似但包路径不同。常用的核心注解就三个Tag标注 Controller 类的用途相当于给一组接口分组起个名字。Operation标注接口方法的说明描述这个接口的功能。Parameter标注参数的含义。举一个具体例子package com.example.demo.controller; import com.example.demo.common.Result; import com.example.demo.entity.User; import com.example.demo.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; Tag(name 用户管理, description 用户相关接口) RestController RequestMapping(/api/users) public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService userService; } Operation(summary 根据ID查询用户) GetMapping(/{id}) public ResultUser getUserById( Parameter(description 用户ID) PathVariable Long id) { return Result.success(userService.findById(id)); } }启动项目后访问http://localhost:8080/swagger-ui.html你就能看到页面展示的所有接口列表。前端不再需要反复来问你“这个参数是必填吗”直接看文档页面就一目了然。这对团队协作效率的提升是实打实的而且有一个巨大好处文档永远和代码同步更新不存在“文档过期”的尴尬。7. 常见问题与排查技巧实录结合我过去带新人和自己写项目踩过的坑把高频问题整理成一个速查表希望能帮你少走一些冤枉路。现象根本原因排查技巧与解决方案启动报Port 8080 was already in use端口被别的进程占用lsof -i :8080Mac/Linux或netstat -anoWindows查看占用进程kill 掉即可数据库连接超时IP、端口、数据库名配置有误或 MySQL 容器未启动优先检查application.properties中的spring.datasource.url用数据库客户端如 Navicat独立测一下连接中文显示乱码数据库连接 URL 未设置 UTF-8 编码在 URL 末尾追加useUnicodetruecharacterEncodingutf8接口返回 500日志显示No qualifying bean of typeService 或 Mapper 没加Service、Repository等注解检查实现类有没有让 Spring 扫描到包路径是否在主类包路径之外API 接口在 Postman 里正常前端却报跨域错误预检请求未被允许后端配置一个 WebMvcConfigurer 实现addCorsMappings允许指定来源访问Async方法好像没异步方法被同类内部调用代理失效确保异步方法放在单独的 Bean 中通过注入后调用并记得在启动类加EnableAsync前端提交 JSON 参数后端解析为 null缺少RequestBody注解或 JSON 字段名与 Java 字段名不一致GET 查询参数不需要RequestBodyPOST JSON 数据必须加这个注解且注意字段命名7.1 Cache 穿透、击穿、雪崩的概念终于能看清了在大家完成第一个实战项目开始接触 Redis 缓存时通常会碰到三个长得非常像的概念。这里顺手帮大家做一个清晰区分。缓存穿透客户端请求大量不存在的数据比如一个不存在的用户 ID缓存和数据库中都查不到每次请求都要打到数据库数据库负载骤增。解决办法是缓存中把“查不到”也存一个短时间过期的空值或者用布隆过滤器挡一层。缓存击穿某个热点 key 突然过期同一瞬间有大量请求并发访问这个 key直接打到数据库。解决办法是设置互斥锁或者延长这个热点 key 的有效期不让它过期。缓存雪崩大量 key 在同一时间过期导致大量请求同时落到数据库。解决思路是给缓存过期时间加一个随机因子让过期时间分散开同时通过限流兜底。这三个概念听上去有些拗口实际上场景都非常具体。等到你真正上线一个有一定流量的项目就会明白它们背后保护的是什么。7.2 开发热部署DevTools 的正确使用姿势Spring Boot DevTools 在开发时能自动重启应用这是一个极大提高效率的工具。但不少新手反映“改了代码没生效”原因往往是没有开启 IDEA 的自动编译。在 IDEA 中进入Settings→Build, Execution, Deployment→Compiler勾选Build project automatically。然后按Ctrl Shift AMac 为Cmd Shift A搜索Registry回车勾选compiler.automake.allow.when.app.running。改完这两步DevTools 才能在代码保存后自动重启应用。还有一个习惯值得注意开发阶段热部署很方便但真正上线部署时用Spring Boot DevTools不仅没有价值还可能在类加载器层面产生奇怪的问题。生产环境启动时去掉这个依赖即可。8. 部署与扩展让你的接口服务从本地跑到服务器8.1 打包成可执行的 Fat Jar本地开发验证完毕最后要面临的就是部署上线。Spring Boot 最爽的一点是它支持打一个可执行的 Fat Jar这个 Jar 包中除了你的业务代码还包含了内嵌的 Tomcat 和所有依赖。这意味着服务器上只要有 JDK不需要额外安装任何中间件直接运行这个 Jar 包就能启动服务。打包方式用 IDEA 右侧 Maven 工具面板执行package生命周期即可。或者命令行执行mvn clean package -DskipTests打包完成后在target目录下能看到一个形如demo-0.0.1-SNAPSHOT.jar的文件。上传到服务器执行java -jar demo-0.0.1-SNAPSHOT.jar服务就起来了。这是新手第一次感受到“把一个网站变成真正可访问”的过程那酸爽真的是写再多代码也无法替代的。8.2 多环境配置dev/prod 环境的优雅切换实际项目中本地开发用的数据库地址、测试环境用的地址、生产环境用的地址肯定不一样。如果你每次部署前都要去改一遍配置文件不仅效率低还很容易出事故。Spring Boot 对这种场景有非常优雅的处理方式配置文件命名规范。比如你的主配置是application.yml可以再创建application-dev.yml开发环境和application-prod.yml生产环境。在主配置文件中spring: profiles: active: dev指定使用哪套配置。实际部署时如果不打物理包也可以在启动命令里强制覆盖java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod这比在代码里写if (env.equals(prod))这种大判断干净太多。不同的环境配置只要保证字段一致切换起来只需要一行命令。8.3 WebSocket给网页加一个实时通道最后补充一个很多新项目会遇到的场景WebSocket 集成。虽然这项技术在最初的学习阶段不是核心但如果你之后的项目需要做在线聊天、消息推送、实时通知就会需要它。这一技术本质上是在 HTTP 请求之外建立一条长连接让服务器可以随时主动给浏览器推送消息而不是每次都要等浏览器先发起请求。在我早期做的一个协作工具里有人给某个任务添加了评论所有看这个任务的其他人需要立刻看到提醒用轮询接口的方式太浪费资源最后引入 WebSocket 才解决了。Spring Boot 2.1 集成 WebSocket 并不复杂。核心做法是写一个端点类继承TextWebSocketHandler重写afterConnectionEstablished、handleTextMessage等方法然后通过一个配置类WebSocketConfigurer把端点和路径注册一下。不过要注意WebSocket 在无状态的标准 HTTP 模式下会话管理是个相对繁琐的工作需要自己维护 session 集合处理心跳和断线重连。如果你所在团队项目规模不大也可以先考虑用第三方现成的推送服务等业务规模真的需要再引入自研长连接这些成本都是要算的。9. 写在最后的个人体会我见过很多刚入门的新人卡在“看完了基础语法却不知道下一步做什么”这个阶段。网上几十小时的视频课看的时候觉得懂了自己动手就抓瞎真正上手写代码时发现脑子里只剩一团模糊的概念。这个问题的根源在于缺乏一个完整的项目脉络。你掌握的每一个知识点都只知道它是“干什么的”却不知道它“放在哪里”。所以这份学习指南的实质就是帮助你搭建一条“环境 → 接口 → 数据库 → 异步 → 文档 → 部署”的完整链路。把这条链路打通你再去看任何框架都会自动地把它安插到这个链条的对应位置上不会再觉得框架是分散的、孤立的技术点。如果你现在还在犹豫要不要开始我的建议是别犹豫太久打开 Spring Initializr花二十分钟把环境跑起来先写一个返回 “Hello World” 的接口。无论你将来会不会成为一名全职程序员这个过程本身都能让你获得一种确定性的掌控感你让一台服务器按照你的想法对世界做出了响应这种感觉还是挺奇妙的。另外再分享一个我后来才意识到的小技巧学习新框架时别只盯着框架本身还要把它周边配套的东西看懂。比如 Spring Boot 永远离不开 Maven、数据库连接、部署服务器。这些配套组件看起来不起眼实际上才是项目真正能走起来的关键。把整条链路疏通你学习的进度会突然变得快起来。
返回列表