Spring全家桶学习路线:从IOC到微服务的实战指南 1. 从一份意外流出的笔记说起Spring 全家桶到底该怎么学先坦白一件事我在一个技术交流群里看到有人转了一份据说是某大厂资深工程师整理的 Spring 全家桶学习笔记当时第一反应是“又是标题党”。但点进去翻了几页之后我发现这份东西确实有点东西——它不是那种把官方文档复制粘贴一遍的流水账而是按照“实际项目里会怎么用”这个逻辑重新组织了一遍。我花了大概两周时间把里面的内容对照着自己的项目过了一遍又补了不少自己的理解才有了今天这篇东西。Spring 全家桶这个词其实没有一个严格的定义。通常来说它指的是以 Spring Framework 为核心向外延伸到 Spring Boot、Spring MVC、Spring Data、Spring Security、Spring Cloud 这一整套技术栈。你随便打开一个招聘网站Java 后端岗位的任职要求里几乎都会出现“熟悉 Spring 全家桶”这几个字。但问题在于很多人学 Spring 的方式是割裂的——今天看两集 Spring IOC 的视频明天翻几页 Spring Boot 的文档后天又去折腾一下 Spring Cloud 的注册中心。学完之后脑子里全是零散的知识点真到了项目里要搭一个完整的后端服务还是不知道从哪下手。这份笔记最大的价值就是它把“学”和“用”之间的那道墙给拆了。它不会一上来就给你讲 Bean 的生命周期有多少个步骤而是先告诉你一个典型的 Web 项目从请求进来到响应回去中间到底经过了哪些 Spring 的组件每个组件负责什么你需要在哪个环节写什么代码。这种“从场景倒推技术点”的思路对于已经有一定 Java 基础但缺乏项目经验的人来说效率比按部就班地啃文档要高得多。我写这篇东西的目的不是要把那份笔记原封不动地复述一遍——那样既不负责任也没多大意思。我想做的是结合我自己在几个实际项目里踩过的坑把 Spring 全家桶的学习路径和核心要点重新梳理一遍。无论你是刚学完 Java SE 准备往 Web 方向走的学生还是工作了一两年但一直在做 CRUD 想系统提升的开发者应该都能从里面找到对自己有用的东西。接下来的内容会按照“整体设计思路→核心组件拆解→实操搭建→问题排查”这个顺序展开每一部分我都会尽量说清楚“为什么这么做”而不仅仅是“怎么做”。2. 整体学习路径的设计逻辑为什么不能按文档目录来学2.1 官方文档的编排逻辑和实际需求的错位如果你打开 Spring 的官方文档会发现它的组织方式是按照模块来划分的Core Container、Data Access、Web、Integration、Testing 等等。这种编排方式对于已经熟悉 Spring 的人来说很方便查阅但对于初学者来说有一个致命问题——它假设你已经知道了每个模块是用来解决什么问题的。比如 Core Container 里的 IOC 和 AOP文档会详细告诉你 BeanFactory 和 ApplicationContext 的区别但它不会告诉你在一个真实的项目里你其实很少直接和 BeanFactory 打交道绝大多数时候你用的是 ApplicationContext 的各种实现类而且这些实现类通常是由 Spring Boot 自动配置好的。这就导致一个很常见的现象很多人学完了 IOC 的概念知道了依赖注入的好处但真到了写代码的时候还是在用new关键字创建对象。因为没有人告诉他在 Spring Boot 项目里你只需要在类上加一个Service或者Component注解然后在需要的地方用Autowired或者构造器注入Spring 就会帮你管理这个对象的生命周期。文档里当然写了这些注解的用法但它不会用“你以前是怎么做的现在应该怎么做”这种对比的方式来讲解。那份笔记的做法是反过来的。它先给你看一个最简单的 Spring Boot Web 项目长什么样一个启动类一个 Controller一个 Service一个 Repository。然后问你如果没有 Spring这四个东西你要怎么串起来你需要自己写一个工厂类来创建 Service 实例自己处理 Controller 和 Service 之间的依赖关系自己管理数据库连接的打开和关闭。然后它再告诉你Spring 帮你做了哪些事情你只需要加几个注解就能达到同样的效果。这种“先看到问题再看到解决方案”的顺序比“先学概念再找场景”要自然得多。2.2 从单体到微服务的渐进式路线另一个我觉得很重要的设计思路是不要一上来就学 Spring Cloud。我见过不少人的学习路线是这样的Java 基础→Spring IOC→Spring MVC→Spring Boot→Spring Cloud。这个路线本身没问题但问题在于很多人在学 Spring Cloud 的时候其实并没有真正理解为什么需要微服务。他们只是听说“现在都流行微服务”所以就去学注册中心、配置中心、网关、熔断限流这些东西。结果学完之后在一个小项目里硬生生拆出七八个服务每个服务就两三个接口反而把系统搞得无比复杂。那份笔记里有一个观点我特别认同Spring Cloud 不是学出来的是逼出来的。当你只有一个单体应用的时候你根本不需要服务发现因为所有代码都在一个进程里直接方法调用就行了。只有当你的应用大到一定程度——比如团队规模超过十个人代码库大到每次发布都要等半个小时某个模块的流量暴涨导致整个系统崩溃——你才会真正需要微服务的那套东西。所以它的建议是先把 Spring Boot 用熟能独立完成一个中等复杂度的单体应用然后再去了解 Spring Cloud 的各个组件分别解决什么问题。而且了解的时候不要只看它怎么用要看它在没有微服务框架之前这些问题是怎么解决的。举个例子服务注册与发现。在没有注册中心之前你是怎么让服务 A 调用服务 B 的你可能会把服务 B 的地址写在配置文件里然后通过 HTTP 客户端去调用。这种方式在服务 B 的地址不变的情况下是没问题的但如果服务 B 部署了多个实例或者地址发生了变化你就需要手动去改配置。注册中心解决的就是这个问题服务 B 启动的时候把自己的地址注册到注册中心服务 A 从注册中心拉取服务 B 的地址列表然后自己决定调用哪一个。理解了这一点你再去学 Nacos 或者 Eureka 的用法就会觉得顺理成章而不是死记硬背。2.3 版本选择为什么我不建议用最新的还有一个很实际的问题Spring 的版本迭代非常快。你搜一篇两年前的技术文章里面的代码可能已经跑不起来了。那份笔记里提到了一个原则我觉得很实用学习的时候选择当前主流稳定版本的上一个版本。比如现在 Spring Boot 3.x 已经比较成熟了但如果你是为了学习用 2.7.x 可能会更顺利。原因有几个第一网上大部分教程和解决方案都是基于 2.x 的遇到问题更容易搜到答案第二Spring Boot 3.x 要求 Java 17 以上而很多公司的生产环境还在用 Java 8 或 Java 11第三3.x 里有一些破坏性的变更比如 Jakarta EE 的包名从javax变成了jakarta如果你跟着老教程学代码会报错反而增加学习成本。当然如果你是在做新项目或者公司要求用新版本那直接上 3.x 也没问题。但如果是学习阶段我建议先用 2.7.x 把核心概念跑通然后再去看 3.x 的迁移指南了解有哪些变化。这样你的学习曲线会更平滑。3. 核心组件拆解IOC、AOP、MVC 到底在解决什么问题3.1 IOC 容器从手动管理对象到自动装配IOC 的全称是 Inversion of Control中文叫控制反转。这个名字听起来很玄乎但其实它描述的是一个很朴素的事实以前是你自己创建对象现在是把创建对象的控制权交给框架。举个例子假设你有一个UserService类它需要依赖一个UserRepository来访问数据库。在没有 IOC 的情况下你可能会这样写public class UserService { private UserRepository userRepository new UserRepository(); public User getUserById(Long id) { return userRepository.findById(id); } }这段代码的问题在于UserService和UserRepository是硬编码的耦合关系。如果有一天你想换一个UserRepository的实现或者想在测试的时候用一个 Mock 对象替换掉真实的数据库访问你就必须修改UserService的代码。这违反了开闭原则。有了 IOC 容器之后你只需要在类上加上注解容器就会帮你创建和管理这些对象Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository userRepository; } public User getUserById(Long id) { return userRepository.findById(id); } }容器在启动的时候会扫描所有带有Service、Repository、Component等注解的类创建它们的实例然后根据构造函数的参数类型把对应的依赖注入进去。这个过程叫做“依赖注入”它是实现 IOC 的一种方式。那份笔记里有一个比喻我觉得很形象IOC 容器就像一个婚介所。你不需要自己去满世界找对象只需要告诉婚介所你的要求它就会帮你匹配。匹配好了之后你们就可以直接交往了不需要关心对方是怎么被找到的。当然这个比喻有点浪漫化了实际工作中你还是要理解容器的工作原理否则遇到循环依赖、Bean 初始化顺序这些问题的时候你会完全不知道从哪里下手。关于 Bean 的作用域默认是单例Singleton也就是说整个容器里只有一个实例。这在大多数情况下是没问题的因为 Service 和 Repository 这类对象通常是无状态的。但如果你在 Bean 里定义了可变的成员变量就要小心线程安全问题。比如下面这个例子Service public class CounterService { private int count 0; public int increment() { return count; } }这个 Bean 是单例的多个线程同时调用increment()方法时count的值可能会出错。正确的做法是使用AtomicInteger或者把 Bean 的作用域改成prototype。但改成prototype之后每次注入都会创建一个新的实例这可能会带来性能问题。所以更好的做法是保持单例但不要在 Bean 里保存可变状态。3.2 AOP把横切关注点从业务逻辑里抽出来AOP 的全称是 Aspect-Oriented Programming面向切面编程。它要解决的问题是有些代码在很多地方都会出现但它们和核心业务逻辑没有直接关系。比如日志记录、事务管理、权限校验、性能监控。这些代码如果散落在各个方法里会让业务逻辑变得很臃肿。举个例子假设你有一个订单服务里面的每个方法都需要记录日志public class OrderService { public void createOrder(Order order) { logger.info(开始创建订单: {}, order); // 业务逻辑 logger.info(订单创建完成: {}, order.getId()); } public void cancelOrder(Long orderId) { logger.info(开始取消订单: {}, orderId); // 业务逻辑 logger.info(订单取消完成: {}, orderId); } }如果每个方法都要这样写代码会变得非常重复。用 AOP 的话你可以把日志逻辑抽到一个切面里Aspect Component public class LoggingAspect { Around(execution(* com.example.service.*.*(..))) public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { logger.info(开始执行: {}, joinPoint.getSignature()); Object result joinPoint.proceed(); logger.info(执行完成: {}, joinPoint.getSignature()); return result; } }这样OrderService里的方法就不需要再写日志代码了切面会自动在方法执行前后插入日志。AOP 的底层实现通常是动态代理如果目标类实现了接口就用 JDK 动态代理如果没有实现接口就用 CGLIB 生成一个子类。Spring Boot 默认使用 CGLIB因为它在大多数情况下都能工作而且不需要目标类实现接口。那份笔记里特别提醒了一点AOP 虽然好用但不要滥用。有些开发者会把所有东西都往切面里塞结果代码变得非常难调试。因为切面的执行是隐式的你看到方法调用的时候不知道背后还有哪些切面在起作用。所以我的建议是只把那些真正通用的、和业务无关的逻辑放到切面里比如日志、事务、监控。业务相关的逻辑哪怕重复一点也最好写在业务代码里这样更直观。3.3 Spring MVC一个请求的完整旅程Spring MVC 是 Spring 全家桶里最常用的模块之一它负责处理 Web 请求。很多人用 Spring MVC 就是写几个RestController和GetMapping但如果你不了解一个请求从进入应用到返回响应中间经过了哪些组件遇到问题的时候就很难排查。一个典型的请求处理流程是这样的客户端发起 HTTP 请求请求首先到达DispatcherServlet它是整个流程的调度中心。DispatcherServlet会查询HandlerMapping找到能处理这个请求的 Controller 方法。然后它会调用HandlerAdapter来执行这个方法。在方法执行之前和之后可能会有拦截器Interceptor介入。方法执行完成后返回一个结果如果是RestController结果会被HttpMessageConverter转换成 JSON 或其他格式然后写回响应。这个流程里有几个地方是容易出问题的。第一个是HandlerMapping找不到匹配的处理器这通常是因为 URL 写错了或者 Controller 没有被扫描到。第二个是参数绑定失败比如你期望一个Long类型的参数但客户端传了一个非数字的字符串这时候会抛出MethodArgumentTypeMismatchException。第三个是HttpMessageConverter转换失败比如你返回了一个无法序列化的对象或者没有引入 Jackson 依赖。那份笔记里有一个排查技巧我觉得很实用在application.yml里把 Spring MVC 的日志级别调到DEBUG这样你可以看到每个请求匹配到了哪个 Controller 方法参数是怎么绑定的返回值是怎么转换的。具体配置如下logging: level: org.springframework.web: DEBUG org.springframework.web.servlet.mvc.method.annotation: DEBUG开启之后控制台会输出类似这样的日志DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Looking up handler method for path /users/1 DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Returning handler method [public com.example.User com.example.UserController.getUser(java.lang.Long)] DEBUG o.s.w.m.m.a.RequestResponseBodyMethodProcessor - Writing [User{id1, name张三}] as application/json这些日志能帮你快速定位问题如果第一步就没有输出说明请求没有匹配到任何 Controller如果第二步输出了但第三步没有说明方法执行过程中抛异常了如果第三步输出了但客户端收到的响应不对说明是序列化的问题。4. 实操搭建从零开始构建一个完整的后端服务4.1 项目初始化用 Spring Initializr 还是手动配置搭建 Spring Boot 项目有两种常见方式一种是用 Spring Initializr网页版或者 IDE 插件另一种是手动创建 Maven 项目然后自己加依赖。我两种都用过说说各自的适用场景。Spring Initializr 的好处是快你选好 Spring Boot 版本、Java 版本、需要的依赖它就会生成一个完整的项目结构包括pom.xml、启动类、配置文件、测试类。对于新项目来说这是最省事的方式。但它也有一个缺点生成的pom.xml里会包含一些你可能用不到的依赖和插件而且版本号是固定的如果你想升级某个依赖需要手动去改。手动配置的好处是你对项目的每个细节都了如指掌。你知道每个依赖是干什么用的为什么要加它。这对于学习来说其实更有价值。下面是一个最小化的pom.xml示例parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency /dependencies这里我用了 H2 内存数据库这样你不需要安装 MySQL 就能跑起来。等核心功能跑通了再把数据库换成 MySQL 或者 PostgreSQL。这种“先用最简单的方案验证再逐步替换成生产级方案”的思路可以让你把注意力集中在 Spring 本身而不是被环境问题分散精力。4.2 分层结构Controller、Service、Repository 的职责划分一个标准的 Spring Boot Web 项目通常分为三层Controller 层负责接收请求和返回响应Service 层负责业务逻辑Repository 层负责数据访问。这个划分方式不是 Spring 强制要求的但它已经成为了事实上的标准。Controller 层的代码应该尽量薄只做参数校验和结果封装不包含业务逻辑。比如RestController RequestMapping(/api/users) public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService userService; } GetMapping(/{id}) public ResponseEntityUserDTO getUser(PathVariable Long id) { UserDTO user userService.getUserById(id); return ResponseEntity.ok(user); } PostMapping public ResponseEntityUserDTO createUser(RequestBody Valid CreateUserRequest request) { UserDTO user userService.createUser(request); return ResponseEntity.status(HttpStatus.CREATED).body(user); } }Service 层包含业务逻辑比如校验用户是否已存在、密码加密、发送通知等。Repository 层通常是一个接口继承JpaRepository或者CrudRepositorySpring Data JPA 会自动生成实现public interface UserRepository extends JpaRepositoryUser, Long { OptionalUser findByUsername(String username); boolean existsByEmail(String email); }这里有一个经验之谈Repository 层的方法命名是有讲究的。Spring Data JPA 会根据方法名自动生成查询语句。比如findByUsername会被翻译成SELECT * FROM users WHERE username ?existsByEmail会被翻译成SELECT COUNT(*) 0 FROM users WHERE email ?。如果你把方法名写成findByUsernameAndStatus它就会生成两个条件的查询。但方法名不能太长否则可读性会很差。如果查询条件超过三个建议用Query注解手写 JPQL 或者原生 SQL。4.3 配置文件application.yml 里的那些坑Spring Boot 的配置文件支持.properties和.yml两种格式。我个人更倾向于.yml因为它的层级结构更清晰尤其是当配置项很多的时候。但.yml有一个很容易踩的坑缩进必须用空格不能用 Tab。如果你从网上复制了一段配置缩进乱了应用启动的时候会报错而且错误信息往往不太直观。另一个常见的坑是配置项的优先级。Spring Boot 支持多种配置来源包括命令行参数、环境变量、application.yml、application-{profile}.yml等。它们的优先级从高到低是命令行参数 环境变量 application-{profile}.ymlapplication.yml。这意味着如果你在application.yml里配置了数据库连接又在环境变量里配置了同名的配置项环境变量会覆盖配置文件里的值。这个特性在容器化部署的时候很有用因为你可以通过环境变量来注入不同环境的配置而不需要修改配置文件。还有一个关于多环境配置的技巧不要把所有的配置都写在一个文件里。通常的做法是创建application-dev.yml、application-test.yml、application-prod.yml三个文件分别对应开发、测试、生产环境。然后在application.yml里通过spring.profiles.active来指定当前激活的环境spring: profiles: active: dev启动的时候也可以通过命令行参数来覆盖java -jar app.jar --spring.profiles.activeprod。这样同一个 jar 包可以在不同环境里运行不需要重新打包。5. 常见问题与排查技巧实录5.1 启动报错Bean 创建失败怎么办Spring Boot 启动失败最常见的原因就是 Bean 创建失败。错误信息通常会告诉你哪个 Bean 创建失败了以及失败的原因。但有时候错误信息会很长嵌套了很多层让人不知道从哪里看起。我的经验是从错误信息的最下面往上找找到第一个Caused by那通常才是根本原因。比如下面这个错误Error creating bean with name userService: Unsatisfied dependency expressed through constructor parameter 0 Caused by: NoSuchBeanDefinitionException: No qualifying bean of type com.example.UserRepository available最下面的Caused by告诉我们容器里找不到UserRepository类型的 Bean。可能的原因有几个一是UserRepository接口没有加Repository注解其实继承JpaRepository之后不需要加Spring Data JPA 会自动扫描二是UserRepository所在的包不在启动类的扫描范围内。默认情况下Spring Boot 会扫描启动类所在包及其子包。如果你的UserRepository在另一个包下面就需要在启动类上加EnableJpaRepositories(basePackages com.example.repository)或者在SpringBootApplication里指定扫描路径。另一个常见的 Bean 创建失败是循环依赖。比如ServiceA依赖ServiceBServiceB又依赖ServiceA。在 Spring Boot 2.6 之前循环依赖是默认允许的Spring 会通过三级缓存来解决。但从 2.6 开始循环依赖默认被禁止了启动时会直接报错。解决循环依赖的方法有几种一是重构代码把循环依赖的部分抽到一个新的类里二是用Lazy注解延迟加载其中一个 Bean三是在配置文件里允许循环依赖不推荐因为这只是掩盖了问题。5.2 接口 404请求为什么没匹配到 Controller接口返回 404 是另一个高频问题。可能的原因包括URL 路径写错了、HTTP 方法不匹配、Controller 没有被扫描到、或者被拦截器拦截了。排查的时候我通常会按照以下顺序来检查检查项可能的问题解决方法URL 路径大小写不一致、多了或少了斜杠对照 Controller 上的RequestMapping和方法的GetMapping拼接后的完整路径HTTP 方法用 GET 请求了 POST 接口检查GetMapping、PostMapping等注解包扫描Controller 不在启动类的子包下把 Controller 移到正确的包下或者配置ComponentScan拦截器拦截器提前返回了 404检查拦截器的preHandle方法静态资源请求被静态资源处理器拦截了检查spring.mvc.static-path-pattern配置如果以上都检查过了还是 404可以在启动类上加EnableWebMvc注解然后看控制台有没有输出映射信息。不过要注意加了EnableWebMvc之后Spring Boot 的自动配置会失效你需要自己配置很多 Web 相关的东西。所以这个方法只适合临时排查排查完要记得去掉。5.3 事务不生效Transactional 的常见陷阱Transactional注解用起来很简单但坑特别多。我见过最常见的问题是在同一个类里一个方法调用另一个带有Transactional注解的方法事务不生效。比如Service public class OrderService { public void createOrder(Order order) { this.saveOrder(order); // 事务不生效 } Transactional public void saveOrder(Order order) { // 保存订单 } }原因是 Spring 的事务是基于 AOP 代理实现的。当你从外部调用createOrder的时候实际上调用的是代理对象的方法。但createOrder内部调用saveOrder的时候用的是this也就是原始对象而不是代理对象所以事务切面不会生效。解决方法有几种一是把saveOrder方法移到另一个 Service 里二是通过AopContext.currentProxy()获取代理对象再调用三是注入自己Autowired private OrderService self;然后用self.saveOrder()调用。另一个常见的坑是事务的传播行为。默认的传播行为是REQUIRED意思是如果当前没有事务就新建一个如果已经有事务就加入当前事务。但有些场景下你可能需要REQUIRES_NEW也就是不管当前有没有事务都新建一个独立的事务。比如记录日志的操作即使业务事务回滚了日志也应该保存下来。这时候就需要把日志方法的事务传播行为设置为REQUIRES_NEW。还有一个容易被忽略的点Transactional默认只对RuntimeException和Error回滚对受检异常Exception的子类但不是RuntimeException不回滚。如果你希望受检异常也触发回滚需要显式指定rollbackFor Exception.class。5.4 性能问题接口响应慢怎么排查接口响应慢的原因可能有很多数据库查询慢、外部服务调用超时、GC 频繁、线程池满了等等。排查的时候我通常会先用spring-boot-starter-actuator暴露一些监控端点看看应用的运行状态。在pom.xml里加上 actuator 依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency然后在application.yml里开启需要的端点management: endpoints: web: exposure: include: health,metrics,httptrace访问/actuator/metrics/http.server.requests可以看到每个接口的请求次数、平均响应时间、最大响应时间等指标。如果某个接口的平均响应时间特别长再去查这个接口的数据库查询。可以在application.yml里开启 JPA 的 SQL 日志spring: jpa: show-sql: true properties: hibernate: format_sql: true这样控制台会打印出每条执行的 SQL你可以看看有没有全表扫描、有没有 N1 查询的问题。N1 查询是 JPA 里很常见的一个性能问题你查询了 N 条主记录然后对每条记录又去查询关联的子记录导致执行了 N1 条 SQL。解决方法是用JOIN FETCH一次性把关联数据查出来或者配置EntityGraph。6. 一些不成体系的个人经验6.1 关于学习节奏不要试图一次学完所有东西Spring 全家桶的内容非常多如果你试图把每个模块都学透了再动手做项目大概率会半途而废。我的建议是先学最核心的 20%也就是 IOC、AOP、Spring MVC 和 Spring Boot 的自动配置。这四块内容能让你搭建一个可运行的 Web 应用。剩下的 Spring Data、Spring Security、Spring Cloud 这些等到项目里真正需要的时候再去学。这种“按需学习”的方式效率比“系统学习”要高得多因为你有具体的场景来验证你学的东西。6.2 关于调试日志比断点更管用很多人调试 Spring 应用的时候喜欢打断点一步一步跟。这在排查简单问题的时候没问题但在排查复杂问题的时候日志往往更管用。因为 Spring 的很多逻辑是异步的、代理的断点跟起来会很累。把相关包的日志级别调到 DEBUG让应用自己告诉你它做了什么通常能更快地定位问题。当然日志也不能乱开否则控制台会被刷屏。我的习惯是只在排查问题的时候临时开启 DEBUG问题解决后就调回 INFO。6.3 关于版本升级不要为了升级而升级Spring Boot 的版本迭代很快几乎每半年就有一个新的 minor 版本。但我的建议是除非新版本解决了你正在遇到的问题或者旧版本已经停止维护了否则不要轻易升级。因为升级可能会引入不兼容的变更你需要花时间去测试和修复。我见过不少团队为了“用上新特性”而升级结果引入了一堆 bug反而得不偿失。如果确实需要升级一定要先在测试环境充分验证并且做好回滚的准备。6.4 一个容易被忽略的细节优雅停机最后分享一个很多人会忽略的配置优雅停机。默认情况下当你停止 Spring Boot 应用的时候它会直接关闭正在处理的请求会被中断。如果你的应用在 Kubernetes 里运行Pod 被删除的时候正在处理的请求就会失败。开启优雅停机之后应用会先停止接收新的请求等正在处理的请求完成后再关闭。配置很简单server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 30s这个配置在容器化部署的时候尤其重要能避免很多因为发布导致的请求失败。我是在一次线上发布之后发现部分请求报错排查了半天才想到是这个原因。加上这个配置之后发布期间的错误率明显下降了。