
上一篇《Spring 核心知识点全解析》发完之后评论和私信里出现频率最高的一批问题我盘点了一下基本都集中在几个点上循环依赖为什么要三级缓存、Spring Boot 自动配置为什么经常“改了没反应”、AOP 日志切面写好了却死活不生效、Spring MVC 参数解析背后到底发生了什么。这些问题属于典型的“面试会考、工作会踩”的内容只看概念只能记住答案真遇到线上故障照样懵。这一篇我就把它们串起来讲透重点放在原理拆解和排障经验上顺手把 Spring Security OAuth2、Spring AI、StateMachine、自定义校验这些相对零散但实战常用的模块也整理一遍。如果你正在啃 Spring 源码、准备面试或者已经被循环依赖和代理失效折磨过这一篇应该能帮你省不少时间。1. 三级缓存与循环依赖从“背概念”到真正理解设计取舍1.1 为什么会出现循环依赖Spring 又是怎么兜住的很多人张口就能背出“三级缓存”是哪三级但问一句“为什么需要第三级”就卡住了。先把问题本身拆开。循环依赖意味着 A 依赖 BB 依赖 A。Spring 默认管理的是单例 Bean单例意味着容器里只有一个实例而这个实例只有被完整创建后才会放进单例池。问题来了创建 A 需要注入 B创建 B 需要注入 A如果严格按照“创建完再放池子”的规则两边都等不到对方创建流程会直接死锁。Spring 的解法是“提前暴露”A 还没完成全部初始化时先把一个早期引用暂存下来让 B 能拿着这个引用先把自己建完最后 A 再继续走完剩余的初始化步骤。这里有一个很容易被忽略的前提循环依赖不是所有作用域都能解。Spring 只能兜住“单例 属性注入setter / 字段”这个组合。构造器注入产生的循环依赖基本无解因为构造阶段连对象实例都还没有生成根本没有可提前暴露的东西prototype 作用域同样无解每次 getBean 都是新建对象不存在暂存早期引用的动作。所以一旦项目里出现构造器注入加循环依赖的组合启动直接报 BeanCurrentlyInCreationException这个异常本质上是设计问题的报警器而不是框架缺陷。注意很多人遇到循环依赖第一反应是改成字段注入让它跑起来我强烈不建议这么做。字段注入只是把问题藏了起来后续每次维护都要面对一个说不清依赖方向的对象图。1.2 每级缓存到底存的是什么第三级为什么必须存在Spring 处理单例 Bean 的缓存体系包含三个 Map理解每一级的职责比记住名字重要得多缓存级别存储内容作用阶段一级缓存 singletonObjects完整初始化后的单例对象每次 getBean 最终命中的地方二级缓存 earlySingletonObjects早期暴露的对象属性可能还未填充循环依赖发生时提前给其他 Bean 使用的引用三级缓存 singletonFactoriesObjectFactory 工厂对象用于按需生成早期对象支持代理的判断创建 A 时发现需要注入 B容器去获取 B创建 B 时发现需要注入 A容器去获取 A。此时 A 不在 singletonObjects也不在 earlySingletonObjects但在 singletonFactories 里存在一个 ObjectFactory于是通过这个工厂拿到 A 的早期引用放进二级缓存同时把三级缓存里的工厂移除。B 拿着这个早期引用完成构建并进入一级缓存然后 A 继续填充剩余属性、执行初始化方法最终也进入一级缓存。为什么三级缓存要保留 ObjectFactory而不是直接把原始对象放进二级缓存这是整个机制里最值得品的地方。因为拿到早期引用时A 可能还没有经历 AOP 代理的创建阶段。Spring AOP 的代理通常是在 Bean 初始化后置处理器里生成的如果二级缓存直接放原始对象提前暴露出去的引用就是未经代理的普通实例后面真正生成代理时已经持有旧引用的 B 拿到的还是原始对象切面逻辑全部失效。三级缓存里的 ObjectFactory 可以在“被引用”的那一刻才判断是否需要生成代理保证提前暴露出去的引用恰好是最终代理对象。这就是三级比二级多出来的价值。1.3 实战中的处理思路与遗留问题排查实际项目里循环依赖常出现在两个方向业务 Service 互相调用以及配置类与 Service 互相注入。前者多数是职责边界没划清楚后者多半是配置类里顺手写了业务逻辑。我建议先把构造器注入想一遍能不能用如果代码能改成构造器注入且不出现循环依赖说明原来是可以拆解的如果改了构造器注入立刻报错那就老老实实梳理依赖关系。调整方向通常有三个。第一把相互调用的逻辑提取到第三方组件或者引入事件机制解耦这是最彻底的做法比如把 A 依赖 B 的那段逻辑下沉到 B 对应的事件监听器里。第二给其中一个依赖加 Lazy延迟它代理对象的初始化让 Spring 在创建时可以先注入一个代理占位符真正调用时才去初始化目标对象。第三如果确实是领域对象之间的双向协作考虑 setter 注入加 Autowired但这只适合少数场景不建议成为默认方案。另外要有一点心理准备加了 Transactional 或 Async 的类循环依赖的行为会变得更微妙。因为这两类功能都依赖代理提前暴露引用时如果代理还没生成拿到的引用可能在后续初始化阶段被替换进而引发“日志切面生效但事务代理失效”这类组合问题。碰到这种场景不要困在三级缓存的字面概念里打开 Spring 的 trace 日志观察 Bean 创建顺序比靠猜快得多。2. Spring Boot 自动配置不仅知道“有”还要学会“查”和“改”2.1 SpringBootApplication 到底打开了什么开关SpringBootApplication 是组合注解主要由 SpringBootConfiguration、EnableAutoConfiguration、ComponentScan 组成。其中最核心的是 EnableAutoConfiguration它通过导入 AutoConfigurationImportSelector 加载自动配置类列表。Spring Boot 2.7 之后自动配置类的注册列表放在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件里2.7 之前则是 META-INF/spring.factories。理解这个机制的意义在于自动配置不是魔法它本质上就是一批“带着条件注解的配置类”。每个自动配置类通常搭配一堆条件注解比如 ConditionalOnClass 判断类路径是否存在某个类ConditionalOnMissingBean 判断容器中没有某个 BeanConditionalOnProperty 判断配置项是否打开。只有所有条件都满足这个配置类中的 Bean 才会被注册。这也是为什么 Spring Boot 能做到“开箱即用”的同时又允许你用自己的 Bean 替换默认实现。2.2 配置改了没反应先查自动配置是否真的生效“我改了 application.yml为什么没变化”是 Spring Boot 社区第一高频问题。大部分情况下不是配置没读到而是对应的自动配置类根本没有激活。举个最典型的例子项目里已经存在自定义的 DataSource BeanSpring Boot 的数据源自动配置看到 ConditionalOnMissingBean(DataSource.class) 不满足就不会创建默认的连接池。此时你在 yml 里修改 spring.datasource.url当然毫无反应。排查这类问题最直接的方法是启动时加 debugtrue。控制台会打出 ConditionEvaluationReport里面分“正向匹配”和“负向匹配”两类每条自动配置都会给出匹配或失败的具体原因。比如你看到 DataSourceAutoConfiguration 匹配失败原因写着 did not find bean DataSource说明容器里没有该类型的 Bean那么问题很可能出在主类扫描范围不对或者自动配置被排除。生产环境还可以引入 actuator通过 /actuator/conditions 端点在线查看所有自动配置的匹配状况。2.3 自定义 Starter理解自动配置的最好实验想真正掌握自动配置自己写一个 Starter 是性价比极高的路径。思路很简单在 META-INF 下放置自动配置文件写一个自动配置类类里面用 Bean 加 ConditionalOnMissingBean 暴露默认实现。需要注意的关键点有两个。第一自动配置类不能被应用主类的 ComponentScan 扫到。如果自动配置类和业务代码放同一个包它会被普通扫描当成普通组件提前注册条件注解的“按需生效”就失去了意义。所以官方推荐自动配置类放在独立的包或者通过自动配置文件注册而不是扫描。第二自动配置类的顺序影响很大。多个 Starter 协作时比如连接池配置和健康检查配置有先后依赖用 AutoConfigureOrder 或 AutoConfigureBefore / AutoConfigureAfter 显式声明顺序能避免非常难排查的启动行为问题。我实际碰到过连接池自动配置先于健康检查执行导致健康检查拿到的是初始化未完成的链路表现是启动偶尔报错、重启后症状消失这类问题不看条件报告很难定位。AutoConfiguration ConditionalOnClass(MessageService.class) public class MessageAutoConfiguration { Bean ConditionalOnMissingBean public MessageService messageService() { return new DefaultMessageService(); } }3. Spring AOP 实战日志记录是入门题代理失效才是真考点3.1 一份可直接落地的日志切面写法先给一个可以直接抄的日志切面再讲里面的坑。假设要给 com.example.service 包及其子包下的所有业务方法打印入参与耗时Aspect Component public class LogAspect { Around(execution(* com.example.service..*.*(..))) public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { String method joinPoint.getSignature().getDeclaringTypeName() . joinPoint.getSignature().getName(); Object[] args joinPoint.getArgs(); long start System.currentTimeMillis(); try { Object result joinPoint.proceed(); System.out.println([ method ] 耗时: (System.currentTimeMillis() - start) ms); return result; } catch (Exception e) { System.out.println([ method ] 异常: e.getMessage()); throw e; } } }这个切面本身没有难度真正让无数人困惑的是切面已经写好了Aspect 也加了为什么方法执行时日志就是不打印这背后的原因几乎都指向同一个词代理失效。3.2 代理不生效的四个高频场景第一个是同类内部调用。Controller 调 service.outer()outer() 内部执行 this.inner()inner 方法上的切面不生效。原因是 Spring AOP 基于代理this 指向的是原始对象而不是代理对象内部调用根本没有经过代理入口。第二个是 private 方法。Spring AOP 无论用 JDK 动态代理还是 CGLIB都无法对私有方法做增强。因为私有方法不参与接口契约CGLIB 的子类也不能重写它。在 private 方法上标注切入逻辑结果只会是静默不生效。第三个是 final 方法或 final 类。CGLIB 通过生成子类来实现代理final 方法无法被重写自然无法增强。如果类被 final 修饰连子类都生成不了。第四个是整个项目根本没启用切面。Spring Boot 默认自动配置会开启 AOP但如果你手动排除或包扫描漏掉了 Aspect切面对象没有注册到容器自然没有效果。判断方法很简单在切面类的构造方法或 PostConstruct 里打一行日志启动时看不到这行日志就说明切面根本没被加载。3.3 自调用问题的最优解法不是炫技是拆职责自调用问题最常见的解决方式有三种。第一种是把 inner() 拆到另一个 Service让切面作用在跨类调用上。第二种是往原类注入 ApplicationContext从容器里取代理对象再调用 inner()。第三种是用 AopContext.currentProxy()但需要在配置类上加 EnableAspectJAutoProxy(exposeProxy true) 打开暴露开关否则 currentProxy() 返回 null。从可维护性角度我强烈推荐第一种。拆方法本质上是把职责边界拆干净既解决了代理失效也让代码结构更好读顺带让单元测试变得更容易。第二种和第三种可以解决眼前问题但会留下“为什么这里要拿代理”“为什么方法内部调用要拐弯”的疑问后续维护者理解成本很高。Transactional 和 Async 也存在完全相同的自调用问题。很多人发现“同一个类里内部调用 Transactional 方法事务回滚没有按预期工作”根因就在这里。事务注解依赖的也是 AOP 代理同类内部调用绕过了代理事务自然不生效。3.4 切面优先级与切入点表达式的易错细节多个切面作用于同一个方法时执行顺序由 Order 决定数值越小优先级越高。比如 Order(1) 的日志切面在外层Order(2) 的事务切面在内层那么日志会先记录请求进来事务完成后日志再记录返回结果。如果你想在日志里记录事务提交后的状态日志切面必须在事务切面外层如果你想记录事务回滚的异常详情顺序调整又会带来完全不同的结果。顺序没有绝对对错但你必须清楚自己日志的语义是什么。切入点表达式也有很多细节。execution(* com.example.service...(..)) 的语义是“com.example.service 包及子包下所有类的所有方法”。想匹配某个方法上的自定义注解要写 annotation(com.example.LogAnnotation)想匹配目标类上带某个注解的所有方法要写 within。很多人把这两个混用导致日志只打了一部分。表达式写完以后建议先配一个空方法加 Pointcut再写个单元测试或者临时接口验证匹配范围避免上线后才发现切面扫多了或扫少了。4. Spring MVC 请求链路从 URL 到方法参数的底层真相4.1 DispatcherServlet 与三大核心组件的分工Spring MVC 的核心是 DispatcherServlet。请求进来后它先通过 HandlerMapping 找到能处理当前请求的 Handler一般是一个 Controller 方法再通过 HandlerAdapter 适配调用这个 Handler。HandlerMapping 最常用的实现是 RequestMappingHandlerMapping它根据 RequestMapping 注解的 URL、HTTP 方法、Headers、Params 等条件建立映射。HandlerAdapter 中最核心的是 RequestMappingHandlerAdapter它负责方法参数解析、方法调用和返回值处理。排查问题时分清楚请求在哪个阶段失败很关键。404 多数发生在 HandlerMapping 阶段也就是压根没找到匹配的映射405 通常也是这个阶段找到了 Handler 但 HTTP 方法不匹配而 400 或 500 往往是 HandlerAdapter 阶段参数解析或方法执行失败。看到异常类型先判断阶段再去看具体堆栈排查效率会高很多。4.2 参数解析器RequestBody 背后发生了什么每个方法参数都对应一个 HandlerMethodArgumentResolver。RequestBody 参数由 RequestResponseBodyMethodProcessor 处理核心逻辑是读取请求体通过 HttpMessageConverter 将 JSON 反序列化为目标对象。Spring Boot 默认引入 Jackson所以常见场景都不需要额外配置转换器。但这个机制里有一个高频踩坑点前端用 application/json 提交后端却用 RequestParam 接收结果拿不到数据。RequestParam 从 query string 或表单体取值不负责解析 JSON body两者是两套完全不同的解析通道。另外如果你自定义了 HttpMessageConverter 并手动注册顺序很重要。Spring 会按顺序遍历转换器第一个能处理当前 Content-Type 的转换器被选中。多个自定义转换器时顺序不对会出现“明明写了转换逻辑但反序列化还是用的默认 Jackson”的诡异现象。如果你希望某个参数类型不需要加 RequestBody 就能自动解析可以实现 HandlerMethodArgumentResolver。比如从 header 中取某个字段构造参数对象在 supportsParameter 中判断参数类型在 resolveArgument 中从 request 取数据构建对象。实现之后通过 WebMvcConfigurer 的 addArgumentResolvers 注册进去Spring 就会在解析该类型参数时调用你的逻辑。4.3 返回值包装与全局异常处理的细节Controller 方法返回对象并标记 ResponseBody 时返回值由 RequestResponseBodyMethodProcessor 处理。它通过 HttpMessageConverter 将对象序列化为 JSON。很多项目要求统一返回 Result 如果每个接口手动包裹容易漏且维护成本高。你可以用 RestControllerAdvice 加 ResponseBodyAdvice 统一处理返回值。使用 ResponseBodyAdvice 时注意几个细节。第一supports 方法里要判断是否需要包装如果接口返回类型本身已经是 Result要跳过否则会产生嵌套包装。第二String 类型需要特殊处理因为 String 默认走 StringHttpMessageConverter它和 JSON 转换器的 content-type 不同处理不当会出现“包装好了但响应变成了 text/plain 且中文乱码”的问题。第三ResponseEntity 这类特殊返回值也要过滤避免影响已经手动控制响应状态的接口。异常处理同理。ExceptionHandler 加 RestControllerAdvice 是标准做法可以捕获异常并返回统一格式。每个 ExceptionHandler 方法可以声明接收异常实例参数也可以注入 HttpServletRequest但每个方法只能捕获一个异常类型多个类型可以写数组。如果你希望所有未捕获异常也返回 JSON 而非默认错误页需要再加一个兜底方法捕获 Exception.class或者在 Web 层配置自定义错误处理逻辑。4.4 请求链路上最容易忽略的编码与精度问题这部分单独拿出来说因为真实项目里出现的频率非常高。中文乱码问题一般都集中在 Content-Type 的 charset。Spring 默认 StringHttpMessageConverter 编码是 ISO-8859-1Spring Boot 中通过 server.servlet.encoding 配置 UTF-8 可以解决大部分场景。但如果你自定义了消息转换器或手动 write 字符串就需要显式设置字符集。BigDecimal 精度问题也很常见。JSON 序列化时如果不做处理前端拿到的数字可能是 0.1 而不是 0.10或者某个大数被转成科学计数法。可以在字段上用 JsonSerialize 指定序列化器也可以在全局 Jackson 配置里统一处理 BigDecimal。日期格式化是另一类高频问题。Spring Boot 默认日期格式不一定符合前端约定用 JsonFormat 或者在 application.yml 中配置全局日期格式都能解决。关键是你要知道优先级JsonFormat 注解的优先级高于全局配置全局配置又高于默认值别配置完全局发现个别字段不合预期又找不到原因。5. Spring 新生态模块OAuth2、Spring AI、StateMachine 与自定义校验的实战要点5.1 Spring Security OAuth2 Authorization Server 的配置要点Spring 官方把 OAuth2 授权服务器独立成了单独的项目 spring-security-oauth2-authorization-server和资源服务器配置分开。授权服务器核心是注册客户端RegisteredClient配置令牌端点、授权码端点、JWK 签名等。开发中最常见的需求是“自定义用户信息”包括往令牌中追加自定义 claims做法是注册 OAuth2TokenCustomizer 类型的 Bean在生成令牌时修改 JwtClaimsSet。如果需要完全自定义认证逻辑可以考虑实现 OAuth2AuthenticationProvider 并替换默认 Provider。授权服务器的安全细节容易被忽略。client_secret 在生产环境一定要加密存储不能明文写在配置里或者个人本地仓库提交出去。令牌端点可以加上登录失败次数限制避免暴力破解。资源服务器配置相对简单引入 spring-boot-starter-oauth2-resource-server指定 jwk-set-uri 即可。但是要注意资源服务器和授权服务器如果部署在同一个项目里路径匹配经常会冲突请求放行规则要明确。实际项目里最稳的方案是网关统一做认证微服务内的资源服务器只做 token 校验业务服务内部不要再写一遍复杂的认证逻辑。5.2 Spring AI 与 Agent 相关集成的现状Spring AI 是 Spring 官方推出的 AI 应用集成框架目前支持 OpenAI、Azure OpenAI、Ollama 等模型。集成方式非常模板化引入 spring-ai-starter配置 api-key 和模型名称然后注入 ChatClient 或者 ChatModel 调用模型接口。它的价值在于把底层 HTTP 调用、请求构造、响应解析抽象出来让开发者不必关心各家模型 API 的差异写代码的手感接近操作 RestTemplate。从项目落地角度看Spring AI 的版本迭代非常快很多类名和方法在 0.x 版本里频繁调整示例代码很容易失效。使用时要先确认项目所用的 Spring Boot 主版本和 Spring AI 版本再去看对应版本的官方示例不能直接复制最新文档里的代码。另外Spring AI 目前更适合做原型验证和轻量集成如果你的 AI 调用链路涉及复杂提示词编排、长期记忆、多轮对话还是需要在前端或应用层增加状态管理不要把业务状态堆在模型调用模板里。5.3 Spring StateMachine状态流转的正确设计与持久化教训Spring StateMachine 提供了状态机模型适合订单状态、审批流等场景。核心概念是 State、Event、Transition、Action。配置方式通过 Builder 定义状态集合、初始状态、事件触发关系以及进入某个状态或执行某个 Transition 时触发的 Action。相比在业务代码里写一堆 if-else 判断当前状态能否执行某个操作状态机能把状态迁移的合法路径集中在可读的配置中维护成本低很多。实际开发中状态机的坑主要在持久化。Spring StateMachine 默认状态保存在内存里进程重启会丢失。要持久化需要在业务表中保存状态字段每次操作前从数据库加载状态并构建状态机然后在状态变更后回写状态。高并发场景下要注意状态机实例的并发更新问题通常需要数据库乐观锁或者分布式锁。另外状态变更之后的业务副作用比如发送消息、记录审计日志建议放在状态机外部的事件监听里而不是写在 Action 内部。Action 内部做副作用容易遇到状态机重试或重复执行时副作用也重复触发的问题。5.4 自定义 ValidatorValid 如何真正生效Spring 的 Bean Validation 基于 Hibernate Validator 实现。Valid 之所以能触发校验前提是方法参数上标注了 Valid 或 Validated同时参数解析器内部会调用 Validator。如果你想自定义校验注解核心是创建一个注解并实现 ConstraintValidator 接口。注解定义中需要指定 Constraint(validatedBy SomeValidator.class)并提供 message、groups、payload 属性。实现类里 initialize 方法获取注解属性isValid 方法返回校验结果。自定义校验器的坑点在于它只对“经过 Spring MVC 参数解析的请求参数”生效。如果你在 Service 内部直接 new 对象再调用校验Valid 不会起作用。Session 级别的校验也需要注意有些项目把 Valid 用在 Controller 方法上却期望它对 Service 方法内部的参数也生效结果当然是无效的。正确的做法是在需要校验的方法参数上加上 Valid 或 Validated并且确保这个方法本身是经过 Spring 代理处理的否则约束不会触发。6. Spring Cloud 微服务选型、快速上手与一线避坑清单6.1 微服务要解决的问题是什么很多人上来就上 Spring Cloud其实先想清楚微服务要解决的是什么独立部署、独立扩展、故障隔离、技术异构。如果你的系统只有两三个业务模块单体架构完全够用强行拆分只会把简单问题复杂化部署成本、运维成本、链路排查成本全部上升。快速上手的路径建议按这四件套走服务注册与发现Nacos、配置中心Nacos Config、客户端负载均衡与声明式调用OpenFeign Spring Cloud LoadBalancer、网关Spring Cloud Gateway。这四个组件的组合就能搭建一套可运行的微服务骨架之后再逐步加入分布式事务、消息队列、链路追踪等更重的东西。6.2 服务注册发现与配置中心的核心注意点Nacos 是目前国内用得最多的选择它同时承担注册中心和配置中心比 Eureka Spring Cloud Config 的组合少维护一套组件。使用 Nacos 时最需要注意的是 namespace、group、dataId 的三层命名逻辑。namespace 做环境隔离比如 dev、test、prodgroup 做业务分组dataId 通常与服务名和 profile 对应。之前很多朋友反馈“配置改了没生效”最后查出来要么是 client 没配置监听要么是 dataId 和代码中 spring.config.import 指定的配置不一致。配置中心的核心价值是让配置具备生命周期动态修改、版本回滚、实时推送。但要注意敏感配置不要以明文形式放在配置中心数据库密码、密钥建议加密存储或者结合配置中心自带的加密插件。启动时如果配置中心连不上Spring Cloud 不会直接崩溃但会有大量重试日志生产环境建议把配置中心地址放到启动参数或 bootstrap 配置里避免硬编码导致迁移困难。6.3 网关与流量治理的正确姿势Spring Cloud Gateway 基于 WebFlux是响应式模型天然适合高并发网关场景。常用能力包括路由、断言、过滤器、限流、鉴权。写过滤器时要注意 GlobalFilter 与 GatewayFilter 的区别GlobalFilter 对所有路由生效GatewayFilter 只对指定路由生效。限流可以用内置的 RequestRateLimiter也可以结合 Sentinel 规则管理。实际经验是网关层尽量保持轻量不要写太重业务逻辑否则网关会变成新的性能瓶颈。另一个容易忽略的问题是认证逻辑放网关后微服务之间的内部调用不走网关如果内部调用也需要鉴权要么在服务间传递 token 并各自校验要么用服务间白名单的方式简化处理。网关层不要做同步阻塞操作WebFlux 的线程模型对阻塞非常敏感一个阻塞调用可能拖垮整个网关。6.4 快速上手的路线与避坑清单如果你准备学习 Spring Cloud 并快速落地建议直接基于 Spring Boot 3.x 和 Spring Cloud 2023.x 开始不要再去看老版本的 Ribbon 配置因为负载均衡已经被 Spring Cloud LoadBalancer 取代。学习路径可以这样规划先写三个 demo 服务一个网关、两个 provider把注册发现、OpenFeign 调用、配置中心串起来再逐步加入 Sentinel 或 Resilience4j 做熔断限流。常见问题原因解决方案网关里调用远程接口导致性能下降阻塞调用占满了 WebFlux 工作线程用 WebClient 替代 RestTemplate避免同步阻塞OpenFeign 调用超时默认 connectTimeout/readTimeout 可能很小同时配置 connectTimeout 和 readTimeout服务名带下划线启动异常某些中间件对下划线支持不佳服务命名一律使用中划线出问题找不到调用链没有接入链路追踪第一时间接入 Sleuth Zipkin 或 Micrometer Tracing写到这里Spring 的知识点仍然列不完。我个人做这些梳理的出发点不是让你背会多少概念而是希望每个知识点都能落到“遇到问题怎么排查、怎么修”的层面。三级缓存的第三级为什么必须存在、AOP 为什么自调用失效、自动配置为什么改了没生效这些想清楚之后Spring 就不再是一堆注解的堆砌而是一条可以预测、可以推理的链路。这也是源码阅读最值得投入的方向。如果这一篇能帮你在下一次面试或者排障的时候从“背过答案”变成“知道为什么”那它就是值得的。后续我还会把 Spring 测试、性能调优、应用部署这几个方向单独拿出来写有需要的朋友可以留意后续更新。