☰
Spring核心原理与实战:从IoC/AOP到三级缓存、微服务与AI应用
2026/10/5 4:12:32 网站建设 项目流程

写《Spring笔记》这个系列,是我这几年开发里一直坚持的事。Spring 这个技术栈,从最开始的 Spring Framework、Spring MVC,到 Spring Boot、Spring Cloud,再到现在的 Spring AI,生态越来越大,但核心还是那套 IoC/AOP 思想。这篇笔记不打算按官网文档复述,只记录我在真实项目中反复用到、面试常问、踩过坑的知识点。刚接触 Spring 的人可以把它当路线图,有几年经验的同事也可以当查漏补缺的清单。我会尽量把案例控制在“拿来就能跑”的程度,而不是只讲概念。

在这份笔记里,我刻意把三级缓存、手写 Spring、Spring Security 这些“硬骨头”放在前面,因为后面微服务和 AI 集成,都是在理解容器和扩展机制之后才顺理成章。标题挂的是 Spring 笔记,内容就不限于某一个模块,凡是日常项目里绕不开的,我都会收进来。快速浏览的话,你可以先看目录,再跳到自己需要的章节。

1. Spring核心原理:从容器到三级缓存,手写一遍才踏实

1.1 IoC容器到底管了什么

IoC(Inversion of Control)不是 Spring 的专利,它只是一种设计思路:你不必在自己代码里 new 出所有依赖对象,而是把对象的创建、装配、生命周期管理交给容器。Spring 容器启动后会读取配置文件或者注解,把每个 Bean 解析成 BeanDefinition,再根据依赖关系创建对象。开发者只需要声明“我需要什么”,容器负责把对应的实例“塞”过来。

我习惯拿外包公司做类比。以前你想干活,得自己去招人、发工资、安排职责;用了 IoC 容器后,你只需要告诉外包公司需要什么角色,外包公司就把人派过来,干完活人由外包公司继续管理。这个类比能解释大多数 IoC 的问题:人会不会太多、什么时候招、怎么协作,都不需要你亲自操心,但前提是你要给出清晰的岗位要求。

Spring 里最底层的容器接口是 BeanFactory,它只提供最基础的 getBean、containsBean 能力。我们日常用到的 ApplicationContext 是它的增强版,额外支持事件发布、国际化、资源加载、自动注册 BeanPostProcessor。Spring Boot 启动时创建的就是 AnnotationConfigApplicationContext 这类 ApplicationContext 实现。面试里问 IoC 和 DI 的差别,我会说 IoC 是大的设计思想,DI 是它在 Spring 里的具体实现手段。

有一点需要注意:不是所有对象都要交给 Spring 管理。像无状态的工具方法、常量类、纯数学计算类,完全可以自己 new,硬塞进容器反而是负担。我见过很多项目把工具类也注册成 Bean,启动没快多少,维护时却要多猜一层依赖关系。我的经验是:跨层协作的对象、需要统一生命周期和增强逻辑的对象才放进容器,其他能省则省。

1.2 Bean生命周期中的扩展点

Spring Bean 生命周期是理解一切“为什么注解生效”“为什么代理失效”的基础。一个普通 Bean 从被创建到销毁,大致经历这些阶段:容器读取配置生成 BeanDefinition,通过反射实例化对象,然后做属性填充,接着回调 Aware 接口,再经过 BeanPostProcessor 的前置处理,执行初始化方法,再经过后置处理,最后才进入可用的单例池。如果配置了销毁方法,容器关闭时再执行销毁逻辑。

开发中常用到的扩展点有 InitializingBean、@PostConstruct、ApplicationContextAware、SmartInitializingSingleton、BeanPostProcessor。Spring 的很多高级特性,比如 @Async、@Transactional,其实都是在 BeanPostProcessor 阶段给原对象生成代理。你如果看到某个自定义注解在同一个类里调用不生效,脑子里第一反应就应该是“代理没经过”,而不是“Spring 坏了”。

为什么面试官爱问生命周期?因为它能串起一连串问题。比如 @Autowired 是在属性填充阶段完成的,AOP 代理是在初始化后阶段完成的。所以一个方法如果是同类里另一个方法调用,它不会经过代理,事务和异步注解都会失效。这类问题不是靠背能解决的,需要理解时机。建议你抽半天时间写个小 BeanPostProcessor 打印每个阶段的日志,跑一遍就全记住了。

1.3 三级缓存解决循环依赖的完整推演

循环依赖最经典的场景是 A 依赖 B,B 依赖 A,而且都通过 setter 注入。Spring 默认单例模式下能兜住这种问题,但原型模式不行,构造器注入也不行。很多同学上来就直接背三个 Map 的名字,结果面试官问“为什么需要三个”就卡住了。我按自己的理解重新推演一遍。

先说三级缓存分别是什么:singletonObjects 存放已经完整创建好的单例 Bean;earlySingletonObjects 存放提前暴露出来的“半成品” Bean;singletonFactories 存放 ObjectFactory,也就是能生成这个 Bean 提前引用的工厂。当 A 开始实例化,Spring 会先把它放进第三级缓存;填充属性时发现需要 B,于是取 B;B 实例化后又发现需要 A,这时在第三级缓存里找到 A 的 ObjectFactory,通过工厂拿到 A 的提前引用,注入给 B。B 完整创建后,A 继续完成自己的属性填充和初始化,最后放入一级缓存。

为什么不能只用一级缓存?因为一级缓存里放的是“完整的 Bean”。A 在属性还没填充完时不能算完整,直接放进一级缓存会让别的线程拿到错误对象。那二级缓存行不行?二级缓存存半成品确实可行,但有一个问题:如果 A 需要 AOP 代理,Spring 希望在 A 被提前引用时,给出去的直接是代理对象,而不是普通对象。如果用二级缓存,就必须在实例化后立刻判断 A 是否需要代理,这样要么对所有 Bean 都提前做代理,开销大,要么代理逻辑写得很别扭。三级缓存里的 ObjectFactory 把“创建代理对象”的操作延迟到有人真正依赖它的那一刻,既保证了性能,也保证代理的创建时机正确。

这也能解释为什么构造器注入的循环依赖救不回来。因为构造器需要的是完整的 A 实例,而三级缓存给的是不完整的提前引用。如果你在项目里遇到 BeanCurrentlyInCreationException,先确认是不是构造器注入循环依赖,是的话用 @Lazy 打破,或者把相互依赖的代码抽到第三个对象里。我更推荐后者,因为循环依赖本身就是设计上该避免的味道。

1.4 手写Spring的路径建议

网上“手写 Spring”的文章很多,但多数人只是复制了一套代码,过几天又忘。我的建议是分三步自己慢慢写。第一步,定义 @Component、@Service、@Autowired 这几个注解,写一个包扫描器,把指定包下的 Class 找出来。第二步,创建 BeanFactory,遍历所有类,先通过无参构造反射创建对象,然后扫描每个对象的字段,看到 @Autowired 就注入依赖。第三步,解决循环依赖,可以用一个“正在创建中”的 Set 标记来判断,再考虑 BeanPostProcessor 扩展。

手写这个微型容器时,你才会真正理解为什么实例化不能一股脑做完。如果 A 创建到一半发现需要 B,B 又回头要 A,如果没有任何“提前暴露”机制,线程会直接死循环。这时候你就会发现“提前把工厂存到一个 Map 里”是多么自然的设计。所以三级缓存不是背出来的,是写出来的。

写完容器之后,再手写一遍 AOP 会非常高效。你只需要在 BeanPostProcessor 阶段判断类上有没有 @Aspect 定义的切点,有则用 JDK 动态代理或 CGLIB 生成代理对象。整个过程代码量其实很小,但做完之后你会对 @Transactional 为什么能回滚、为什么同类调用失效有肌肉记忆。不要一开始就啃 Spring 源码,先写小轮子,再对比源码,效率高很多。

2. Spring生态选型:Boot、Cloud、MVC、Security到底怎么搭

2.1 Spring Boot与Spring MVC的关系

很多新手把 Spring Boot 和 Spring MVC 当成同一个东西,这其实不对。Spring MVC 是 Web 层的框架,核心是 DispatcherServlet,负责把请求路由到 Controller。Spring Boot 是一个运行与自动装配框架,它内置 Tomcat,把 Spring MVC、Jackson、Validation、日志等整合到一起,让你少写一堆 XML。Spring Boot 3.x 对应 Spring Framework 6.x,最大的变化是 API 包名从 javax 换成 jakarta,老代码迁移时要留意。

一个请求进入 Spring MVC 后的流程大致是:先由 DispatcherServlet 接收,HandlerMapping 根据 URL 找到处理器,HandlerAdapter 调用 Controller 里的方法,方法返回数据后通过 HttpMessageConverter 转成 JSON。现在前后端分离场景下,Controller 基本都加了 @RestController,直接响应 JSON,不再走 JSP/FreeMarker 那一套视图解析。你在配置 Swagger 时常用到 @RequestMapping,本质上就是给 HandlerMapping 提供映射信息。

用 Boot 开发时,spring-boot-starter-web 这个依赖会自动把 DispatcherServlet、内嵌 Tomcat、Jackson 配好。如果遇到 404 或者接口返回结构不对,可以先怀疑自动配置有没有生效。特别是你想自定义拦截器、过滤器、CORS 时,容易和 Spring Security 的过滤器链打架。我自己的排查顺序是:先确认 WebMvcConfigurer 有没有生效,再看过滤器顺序,最后才看业务代码。

2.2 Spring Cloud微服务组件怎么选

微服务不是用了 Spring Cloud 就微服务了。整套 Spring Cloud 是解决分布式场景下的服务发现、配置、路由、熔断、链路等问题。常见组件选择是:Nacos 做注册中心和配置中心,OpenFeign 做服务间声明式调用,Spring Cloud Gateway 做入口网关,Sentinel 做流量控制。如果你是老项目,可能还会看到 Eureka、Ribbon、Zuul,这些在维护上已经不那么推荐,新项目可以直接用新组合。

选型时要考虑团队维护成本。Nacos 在中小团队真的很友好,因为它把注册中心和配置中心合在一起,控制台中文,界面清楚。服务间调用,OpenFeign 声明式接口用起来很直观,但要注意超时、重试和熔断的配置,不然下游一慢,上游线程池就被拖垮。网关层建议用 Gateway,基于 WebFlux,性能和 Servlet 模型不同,不要把过滤器逻辑写得和业务一样重。

我见过很多项目一上来就拆十个微服务,结果日志没聚合,链路没追踪,数据库连接乱成一团,最后连一个简单的联调都要拉五个服务。如果你的团队规模不大、业务边界还不够清晰,先保持模块化单体会更稳。等真正出现独立伸缩和独立发布诉求时,再按业务域拆服务。微服务的核心收益是组织和响应速度,不是技术面子。

2.3 Spring Security实现认证授权的核心链路

Spring Security 的主体是一条过滤器链。请求进来后,先经过各种 Filter,比如 UsernamePasswordAuthenticationFilter 负责表单登录,JwtAuthenticationFilter 可以做自定义 Token 解析。认证成功的 Authentication 会被放进 SecurityContextHolder,后续授权时就用这个上下文判断角色权限。授权这层在较新版本里由 AuthorizationManager 负责,老版本是 FilterSecurityInterceptor。

实际项目里最常做的是 JWT 无状态认证。用户在登录接口提交用户名密码,AuthenticationManager 内部通过 UserDetailsService 加载用户,然后交给 PasswordEncoder 校验密码。登录成功后生成 JWT 返回给前端。后续请求带 Token,我们在过滤器里解析 Token,把用户信息塞进 SecurityContext。密码加密推荐 BCryptPasswordEncoder,不要再用 MD5,也不要自己拼接加盐逻辑,框架已经处理好了。

配置 Security 时有几个坑。第一,跨域配置要放在 Security 过滤器链之前,否则会被拦截。第二,公开接口要显式放行,否则会被 302 重定向到登录页,前端收到的是 HTML 而不是 JSON。第三,无状态服务要关掉 Session 和 CSRF,同时重写 AuthenticationEntryPoint 返回统一 JSON 错误体。身份认证不是写个拦截器就完事,Security 的过滤器链是全局的,理解链条再写配置才不会到处踩坑。

2.4 从"Spring Framework 5.3.41下载"说起

很多人在搜 Spring Framework 5.3.41 下载,说明老项目还在用 5.3.x 这条线。5.3.x 是 Spring Framework 的一代经典长期维护版本,对应 Spring Boot 2.7.x,支持 JDK 8 以上。生产环境不要盲目追求最新版本,而应该看自己项目里的 Boot 版本对应哪条 Framework 线。老项目稳定运行,能不动就不动,除非有安全漏洞和关键修复需求。

版本对照大致可以这样记:

Spring Boot 版本Spring Framework 版本JDK 支持Servlet 包名
2.7.x5.3.x8/11/17javax
3.0.x6.0.x17+jakarta
3.2.x6.1.x17+jakarta

实际下载并不需要去官网拿 jar。Maven 项目直接声明依赖,Maven 会自动下载。IDEA 里创建 Spring Boot 项目,可以用 start.spring.io 生成压缩包导入。想看源码也不用额外下载,Maven 仓库里每个 jar 都带 sources,IDEA 点击查看即可。版本选择和下载其实不是难点,难的是理解版本升级带来的包名变更和配置变化。

如果你要读源码,我建议优先读 5.3.x 或 6.x 的稳定版,不要选太新的小版本。源码阅读不是从头到尾看,而是从一段启动调用链开始跟。Boot 3 项目用 AnnotationConfigApplicationContext 刷新容器,顺着 refresh() 方法往下看,能看到一批熟悉的 BeanPostProcessor。源码版本与文档版本对齐,能减少大量困惑。

3. 业务实战:多商户商城、第三方接口、监控,这几个场景最常考

3.1 Spring Boot + MyBatis 跨境多商户商城源码怎么拆

多商户跨境商城这个关键词里有三层意思。第一是多商户,平台上有多个商家,每个商家独立管理商品、库存和订单。第二是跨境,涉及多语言、多币种、跨境支付和物流。第三是技术栈,Spring Boot + MyBatis 是 Java 后端最成熟的组合之一。如果研究开源项目,不用纠结它用了什么炫酷架构,先把多商户的核心模型看懂。

数据库设计上,最基础的表包括用户表、商户表、商品表、SKU 表、订单表、订单明细表、支付流水表、结算表。商户表里会冗余平台入驻状态;商品表要关联商户ID,做数据隔离;订单表必须冗余商户ID、商品快照、币种和汇率。这里最容易被忽略的是“快照”概念:订单生成后,商品名称和价格不应该跟着商品表变动而变动,否则订单历史就失真了。这是做电商系统的通用意识。

多商户场景下的分账逻辑也比较典型。用户支付一笔钱进平台账户,平台需要按商户配置的比例拆给各个商家。这个动作最好不要在下单支付回调里同步做,因为分账链路可能涉及多渠道和失败重试。通常支付回调只负责更新订单状态,然后发一条消息,异步任务去处理分账和结算。用 MyBatis 处理这种复杂 SQL 时,我建议把可读性要求高的长 SQL 放在 XML 里,注解里只放简单查询,方便后期 DBA review。

如果你找开源商城源码学习,重点可以放在四个点:租户隔离怎么做、数据权限怎么控制、分布式锁怎么防超卖、支付回调怎么保证幂等。这四个点能弄明白,整个商城系统的业务价值就吃透了一大半。至于前台页面和后台管理,只是外壳,不是核心。

3.2 对外提供给第三方的接口该放在哪里

这是工程上经常争论的问题。第三方接口如果是指“给外部合作伙伴调用的 API”,我的倾向是优先单独建一个服务,比如 open-api-service,而不是直接塞在业务服务里。原因很直接:安全隔离、独立鉴权、限流、版本管理和监控都比较容易做。如果对外接口和内部业务 Controller 混在一起,一次内部业务改动可能就影响了外部合作方,两边互相踩线。

如果你的项目还很小,至少也要在单体里拆出一个独立模块,用统一前缀比如 /api/open/ 约束。对外接口不能复用内部登录的 Session 或用户体系,应该设计独立的开放平台认证方式:AppId、AppSecret、签名、时间戳防重放、IP 白名单、调用频率限制。Spring Boot 实现这些可以在网关层做过滤器,也可以在服务里用拦截器,但不要把逻辑散落在每个 Controller 方法里,最好统一封装。

接口版本管理也是对外接口里很重要的事。我一般会把版本号放进 URL,比如 /v1/order、/v2/order,这样即使接口协议变化,老合作方还能继续用旧版本。配合 OpenAPI/Swagger 文档,合作方接入会顺畅很多。签名设计上,常见的做法是把请求参数按字典序排序拼接,加上 AppSecret 做 HMAC 或 MD5,服务端再算一遍比较。这里要注意时间窗口不能设太长,防止重放攻击。

什么情况下值得单独拆服务?当外部系统很多、合作方需要自助申请密钥、存在大量异步回调通知时,独立服务是必须的。如果只是给一两个内部兄弟部门临时同步数据,用单体模块加独立鉴权也够。但我的建议是哪怕不单独建工程,也要把“对外 API”的边界划清楚,预留拆分空间。否则等调用方多起来,再想拆就伤筋动骨。

3.3 Spring Boot实现监控的需求与功能清单

Spring Boot 项目做监控,第一反应通常是 Actuator。它暴露了应用内部状态端点,比如 health、info、metrics、loggers、conditions、beans。引入 spring-boot-starter-actuator 后,默认只暴露少数端点,需要显式配置。但生产环境千万不能把 beans、conditions 这类端点直接暴露公网,它们包含类名和条件判断信息,容易被外部探测。常见做法是只开放 health、info、prometheus,再通过防火墙或 Security 限制访问。

典型的监控需求可以整理成一张清单:

监控对象关键指标实现方式
应用存活健康检查Actuator /health
JVM堆内存、GC、线程Micrometer + Prometheus
HTTPQPS、P99 耗时、状态码Tomcat 内置 Metrics
数据库慢 SQL、连接池活跃数MyBatis 插件 + 数据源指标
业务指标下单量、支付成功率自定义 Metrics 埋点

真正落地时,我推荐引入 micrometer-registry-prometheus,把 Metrics 暴露成 Prometheus 格式,然后用 Prometheus 抓取,Grafana 展示。整个过程不需要写太多代码,主要是配置和面板。如果团队还没有全套监控设施,可以先用 Spring Boot Admin,它自带一个服务端界面,能看健康状态、日志、Bean 列表,适合中小项目快速上手。

监控的核心不是“把数据堆在页面”,而是让问题在用户发现之前暴露出来。所以我建议先从健康检查和 HTTP 指标开始,稳定了再加业务埋点。比如订单支付成功这个动作,在业务代码里调用 Metrics.counter("order.payment.success").increment(),再配置一个成功率告警,比看一堆 JVM 指标更有业务价值。数据是手段,止损才是目的。

4. Spring AI与新玩法:Agent、A2A、工作流转Java代码

4.1 Spring AI里的AI Agent该怎么理解

Spring AI 是 Spring 生态里相对年轻但热度很高的模块。它把大模型调用、Prompt 模板、结构化输出、向量检索、Tool Calling 都抽象到了熟悉的 Spring 风格里。第一次用的时候,会觉得它很像 JdbcTemplate:不用关心底层 HTTP 和 JSON 细节,几行代码就能和模型对话。它并不是取代 LangChain 类产品,而是把 Java 后端接入 AI 的门槛降下来。

Agent 这个概念,我把它翻译成“会使用工具的助理”。以前写接口,流程是固定的:先查库存,再写死判断,再调下单。Agent 模式里,你告诉模型一个目标,它可以自己判断需要调用哪个工具。Spring AI 里给 Java 方法加 @Tool 注解,模型就会在合适的时候调用这个方法,就像外部系统给 AI 开了一个“后门”。这大大减少了硬编码分支,适合复杂、开放式的问答和处理场景。

举个例子,做一个商品答疑机器人。你写一个方法,用 @Tool 标注“查询商品库存”,内部查询数据库。模型在收到“这个商品还有货吗”时,会自己决定调用库存工具,再把结果整理成自然语言。开发者做的事情是定义能力、设置权限边界和护栏,而不是把每个用户说法都翻译成 if else。这种思路对老 Java 开发者来说很友好,因为工具方法就是普通 Service 方法,测试也好写。

4.2 Spring AI连接百炼Qwen3.7实战

接入大模型,最直接的路径是找一个国内可访问的模型服务。阿里云百炼是一个模型服务平台,上面有通义千问系列模型,Spring AI Alibaba 也提供了对接支持。你需要准备一个 API Key,然后在 Spring Boot 项目里加入 DashScope 相关的 starter。模型 ID 我通常配置成 qwen3.7,但请以百炼平台上实际分配到的模型 ID 为准。

配置示例大概是这样:

spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen3.7

代码里只需要注入 ChatClient:

@Service public class AiService { private final ChatClient chatClient; public AiService(ChatClient.Builder builder) { this.chatClient = builder.build(); } public String ask(String question) { return chatClient.prompt().user(question).call().content(); } }

这里有几个实操点。第一,API Key 一定放在环境变量或配置中心里,不要提交到 Git。第二,ChatClient 是线程安全的,可以设计成单例,不需要每次 new。第三,生产环境不要只输出最简结果,最好先定义一个统一的响应结构,包含模型回复、token 消耗和状态码。流式输出也可以直接用,ChatClient 返回 Flux,前端用 SSE 接收,体验比等一个长响应好很多。

接入过程里最容易遇到的是超时和限流。模型服务不像普通 REST 接口那么稳定,特别是长文本生成。我通常会给调用加上超时配置和重试策略。重试可以用 Spring Retry,但注意幂等和费用控制,不要对一个可能已经生成一半结果的请求盲目重试。还可以对输入长度做限制,避免一次请求消耗大量 token。

4.3 从Dify工作流到Spring AI Java代码:可行吗?

Dify 这类可视化大模型编排工具,适合业务人员快速搭一个 AI 应用原型。但等你真正上线,往往还是要把这些流程落到 Java 后端里。很多人问 Dify 工作流能不能自动转成 Spring AI 代码,我的回答是可行,但不要期待一键转换。Dify 里的节点和 Spring AI 概念是可以映射的,但最终需要人工翻译成代码和配置。

比如 Dify 里一个“LLM 节点”,在 Spring AI 里就是一次 ChatClient.prompt() 调用;知识检索节点对应 VectorStore 和 Retrieval 逻辑;HTTP 请求节点对应 RestClient;条件分支对应 Java 的 if/switch;问题分类节点可以借助模型结构化输出实现。把这些映射搞清楚了,就能把可视化工作流当成需求文档,而不是把它当成线上可运行的依赖。

我自己的做法是先用 Dify 快速验证 prompt 和工具调用的效果,因为它的调试日志非常直观。效果稳定后,再把 prompt 模板、模型参数、工具函数复制到 Spring Boot 项目里。Prompt 模板建议放在 resources 下的文件或配置里,不要硬编码在 Java 代码中,方便后续调整。工作流里如果有很多相似节点,还可以抽成公共方法,减少重复。

如果你真的要做自动化迁移,可以考虑解析 Dify 导出的 yaml 或 workflow 定义,根据节点类型生成 Java 代码骨架。但这需要投入不少开发成本。一般项目里,我觉得手动映射已经足够,自动化只适合节点规范很统一的平台。本质目标不是“转代码”,而是让业务想法最终成为一个可测试、可维护、可监控的 Java 服务。

4.4 A2A协议带来的变革

A2A(Agent-to-Agent)协议是最近很热的话题。简单理解,它是让不同 Agent 之间互相发现、通信和协作的开放协议。以前每个 Agent 都是独立单兵,能力有限;有了 A2A 之后,一个 Agent 可以把任务拆给另一个 Agent 做。对 Java 后端来说,这有点像 Web 服务时代 REST 协议出现的感觉:先定义能力,再互相调用。

Spring AI 也在往这个方向靠拢。Agent 可以把自己的能力暴露成可调用的端点,其他 Agent 通过协议发现并调用。写 AI 应用的方式,可能越来越像写微服务。每个 Agent 相当于一个独立的业务能力单元,它有描述、有入参、有出参,别的 Agent 能通过注册中心或协议描述文件找到它。分布式系统里那套服务治理经验,在 AI 时代又能复用。

不过现在 A2A 还在早期,普通项目没必要硬上。如果你的业务只是单个助手 + 几个工具,用 Spring AI 的 Tool Calling 完全够。等真正遇到多个异构 Agent 协作,再考虑 A2A。我的判断是,先把单个 Agent 的工具边界、上下文和错误处理做好,协议只是锦上添花。Java 开发者应该关注它,但不要被新词绑架。

5. Spring面试怎么准备:高频题、源码切入点、IDE组合

5.1 从"高级Spring面试题"提炼出五种考法

Spring 面试题看起来多,但归纳下来就是五个方向:容器原理、AOP 与事务、自动装配、微服务、安全认证。容器原理最常问 Bean 生命周期、循环依赖、FactoryBean 和 BeanFactory 的区别。AOP 与事务最常问代理机制、事务传播行为、为什么自调用失效。自动装配高频点是 @EnableAutoConfiguration 和 @Conditional 系列。微服务会问注册中心、配置中心、分布式事务。安全主要问 Spring Security 和 JWT 的实现链路。

举一个例子,面试官问“Spring Boot 自动装配原理”,不要只背“@SpringBootApplication 包含 @EnableAutoConfiguration”。你要能说清楚它通过 classpath 下的 AutoConfiguration.imports 文件加载配置类,再用 @ConditionalOnClass、@ConditionalOnMissingBean 这些条件判断是否生效。如果你能顺手讲一个自己遇到的“自动配置没生效”的排查案例,比背十遍概念都有说服力。

事务方面的高频点也很有意思。默认情况下 @Transactional 只对 RuntimeException 和 Error 回滚,受检异常不会回滚。如果你在方法里 try catch 吞掉异常,事务也不会回滚。同类方法自调用不走代理,所以事务失效。这些细节如果只是背结论,换一个实际场景就懵了。我会建议你写一个小项目,把各种事务失效场景跑一遍,面试时聊起来就是自己的真实经历。

面试准备不要追求题目数量,要学会把业务项目往原理上引。比如你说做过商城,就可以讲下单支付时怎么用分布式锁防止超卖,这自然会引到 Spring 事务隔离级别和 Redis 锁的实现。面试官听到你能从项目反推原理,通常比零散背题更认可。

5.2 读源码从哪些类下手(含ProxyFactory)

读 Spring 源码建议从一次真实的启动过程切入。给 main 方法打一个断点,让它进入 AnnotationConfigApplicationContext 的 refresh() 方法。refresh() 是 Spring 容器初始化的总纲,里面依次处理 BeanFactory 后置处理器、注册 BeanPostProcessor、完成单例 Bean 的初始化。跟着 refresh() 走一遍,你会自然看到配置类解析、组件扫描、事件发布这些过程。

如果你对 AOP 底层感兴趣,入口是 AnnotationAwareAspectJAutoProxyCreator。这个类本身是一个 BeanPostProcessor,在 Bean 初始化阶段会判断当前 Bean 是否匹配切点,匹配就创建代理。它内部调用 AbstractAutoProxyCreator 的 wrapIfNecessary,最终通过 ProxyFactory 决定用 JDK 动态代理还是 CGLIB。热搜里的 ProxyFactory 就是这一层的关键类。

读源码有个技巧:不要按类名漫无目的地跳,而是跟着一条调用链反复打断点,观察关键变量的变化。比如看 Bean 实例化,就看 DefaultSingletonBeanRegistry 的 getSingleton 方法,那里能直观看到三级缓存。看代理创建,就看 ProxyFactory,看它怎么根据接口和配置选择代理策略。看完几个核心方法之后再横向扩展,效率会比从头读高很多。

源码阅读的目的不是背类名,而是建立“报错定位”的能力。比如遇到 BeanNotOfRequiredTypeException,你要能想到可能和代理方式有关;遇到循环依赖报错,你要能想到提前暴露机制。源码读多了,报错栈里的类名就不再是乱码,而是一张地图。

5.3 IDEA社区版使用Spring Boot的开发配置

很多初学者以为用 IDEA 社区版就不能开发 Spring Boot,这是误会。社区版免费,没有官方提供的 Spring Initializr 向导,但你完全可以在浏览器打开 start.spring.io,按需求勾选依赖,生成项目压缩包,再在 IDEA 里作为 Maven 项目打开。另外也可以装 Spring Assistant 插件,提供部分 Spring 向导能力。

开发配置上,我用社区版时主要做三件事。第一,确认 JDK 配置。Spring Boot 3 需要 JDK 17,所以项目 SDK 要选对。第二,配置 Maven,setting 文件里可以设置镜像和本地仓库,下载依赖会更顺畅。第三,安装 Lombok 插件并开启注解处理。这些都配置好之后,直接运行带有 @SpringBootApplication 的 main 方法,应用就能启动。

社区版和付费版相比,缺少的是 Spring 专属面板,比如 Boot Dashboard、自动装配可视化。但对日常开发影响不大,因为我们还能用 Maven 命令执行 clean package,用 Run Configuration 启动,调试和热部署都不受限。Spring Boot DevTools 和远程调试在社区版里同样能用。如果团队已经买了 Ultimate,也不是必须,社区版完全适合个人学习和中小项目开发。

我见过很多人纠结“换工具”而不是“开始写代码”。其实工具只是辅助,重点是你能跑通一个接口、理解一个注解、定位一个报错。IDEA 社区版 + Spring Boot + MySQL 这套组合足够支撑你入门到入行。

5.4 一条可执行的学习路线

Spring 学习路线我建议按阶段走,别一上来就啃高深理论。第一阶段,花两周学 Spring Core 和 Spring Boot 基础,重点理解 IoC、AOP、自动装配,可以用一个 REST 接口练手。第二阶段,学 Spring MVC 和 MyBatis,做一个带增删改查的小项目,比如简单商城或博客。第三阶段,加入 Spring Security、测试和缓存,给项目做登录鉴权和接口测试。第四阶段,再接触 Spring Cloud,拆两个服务走通调用。第五阶段,阅读源码和 Spring AI 应用。

把每个阶段的完成标准写下来,会更清楚:

阶段目标验证方式
第一阶段理解 IoC/AOP/Bean 生命周期手写简单 IoC 容器
第二阶段掌握 Boot+MVC+MyBatis完成一个 CRUD 接口
第三阶段掌握 Security+测试给项目加 JWT 登录
第四阶段掌握 Spring Cloud 基础两个服务走通调用
第五阶段源码和 Spring AI 扩展能解释自动装配原理

学习过程中最重要的一件事是记笔记。不用追求大而全,只记录“今天踩了什么坑、报了什么错、怎么解决的”。我写这个 Spring 笔记系列,本质上就是这种记录方式的持续输出。看到热搜里那么多问题,其实大多数都能从自己的项目实践里找到答案。动手写,才是学 Spring 最快的路。

最后说一点个人体会。我每年都会回头看自己写的 Spring 笔记,很多当时觉得莫名其妙的报错,后来都能用 Bean 生命周期或代理机制解释。比如那个经典的循环依赖,光看原理觉得简单,真在项目里遇到 AbstractBeanDefinition 相关报错,还是要靠对三级缓存的理解才能快速判断改哪一行代码。我会继续在 Spring 笔记里补充实战案例,尤其是 Spring AI 这块,变化确实很快。建议你也把遇到的问题一条条记下来,半年后再看,会发现学习曲线比想象中陡,也比想象中扎实。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询