← 返回 Java核心图解 目录

🍃 Spring深度图解

7大核心机制 · SVG可视化 · 面试必考

① 循环依赖三级缓存图解

🔄 什么是循环依赖?

A 依赖 B,B 又依赖 A,形成环路。如果直接创建,就会无限递归。Spring 用三级缓存解决这个问题。

公司入职类比:
新员工A入职,需要IT同事B帮忙开通邮箱→但B也在入职中,需要A帮忙领工牌→两人互相等对方。
Spring 的解法:A 先"半成品"登记(提前暴露),让 B 能找到 A,B 完工后,A 再回来完善自己。
Spring 循环依赖解决:三级缓存流程 一级缓存:singletonObjects 完整的 Bean(已初始化) 二级缓存:earlySingletonObjects 提前暴露的 Bean(半成品) 三级缓存:singletonFactories Bean 工厂(ObjectFactoryλ) 创建 A(A 依赖 B,B 依赖 A)的完整流程: ① 实例化 A(构造器) A 是半成品(属性未填充) ② A的工厂放入三级缓存 singletonFactories.put(A) ③ A 填充属性 → 需要 B 发现 B 还没创建 ④ 实例化 B(构造器) B 的工厂放入三级缓存 ⑤ B 填充属性 → 需要 A A 还没创建完!怎么办? ⑥ 从三级缓存获取 A 的工厂 调用 ObjectFactory → 可能返回代理 ⑦ A 的半成品放入二级缓存 earlySingletonObjects.put(A) ⑧ B 完成初始化 B → 一级缓存 ⑨ A 继续完成初始化 A → 一级缓存,清二三级 ❓ 为什么需要三级? 一级:存成品 二级:存半成品 三级:存工厂 λ 三级缓存的工厂可以决定: 返回原始对象 or 代理对象 没有三级缓存 → AOP 时 B 拿到的 A 和最终的 A 不是同一个代理对象!

图1:Spring 三级缓存解决循环依赖的完整流程

三级缓存解决不了构造器循环依赖!如果 A 的构造器就需要 B,B 的构造器又需要 A,连半成品都创建不出来,Spring 会抛出 BeanCurrentlyInCreationException。解决方案:改用 setter 注入或用 @Lazy 延迟加载。

🔍 三级缓存核心源码

DefaultSingletonBeanRegistry.java
📁 org.springframework.beans.factory.support
DefaultSingletonBeanRegistry
// 一级缓存:完整的 Bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存:提前暴露的半成品 Bean private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存:Bean 工厂(lambda) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 获取 Bean 的逻辑(简化) protected Object getSingleton(String beanName, boolean allowEarlyRef) { // 1. 先查一级缓存 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && this.singletonsCurrentlyInCreation.contains(beanName)) { // 2. 再查二级缓存 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyRef) { // 3. 最后查三级缓存 → 调用工厂 ObjectFactory<?> factory = this.singletonFactories.get(beanName); if (factory != null) { singletonObject = factory.getObject(); // 三级 → 二级(升级) this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } return singletonObject; }
为什么需要二级缓存?只有一级和三级不行吗?

如果只有一级和三级:每次从三级缓存获取都会调用工厂 lambda,可能创建多个不同的代理对象!二级缓存保证同一个半成品 Bean 只被创建一次,所有依赖者拿到的都是同一个引用。

Spring Boot 2.6+ 默认禁止循环依赖?

是的!Spring Boot 2.6 开始默认 spring.main.allow-circular-references=false。Spring 官方认为循环依赖是设计缺陷,应该通过重构(提取公共逻辑到第三个 Bean)来消除,而不是靠三级缓存来"掩盖"。

② 事务传播行为 7 种图解

📊 @Transactional 的 7 种传播行为

事务传播行为定义了"一个事务方法被另一个事务方法调用时"应该怎么做。这是 Spring 事务最核心也最常考的知识点。

公司报销类比:
你已经在走部门A的报销流程(当前事务),现在又要走部门B的流程(新事务方法调用):
- REQUIRED:B加入A的流程,一起签(同一事务)
- REQUIRES_NEW:B另起一个独立流程,签完再回来(挂起当前事务)
- NESTED:B在A流程里嵌套一个子流程,B失败不影响A(保存点)
- NOT_SUPPORTED:B不走流程,直接办(挂起事务)
Spring 事务传播行为 7 种全景图 ① REQUIRED(默认)⭐ 方法A 事务T1 方法B 加入T1 有事务就加入,没有就新建 → 最常用! ② REQUIRES_NEW ⭐ T1(挂起) 方法B 新建T2 T1(恢复) 总是新建独立事务,挂起当前事务 ③ NESTED 方法A 事务T1 嵌套T2(Savepoint) 在当前事务内建保存点,子事务可独立回滚 ④ SUPPORTS 有事务 → 加入 没事务 → 非事务执行 随遇而安,有就加入,没有也行 ⑤ NOT_SUPPORTED T1(挂起) 非事务执行 总是非事务执行,挂起当前事务 ⑥ MANDATORY 有事务 → 加入 没事务 → 抛异常! 强制要求事务,没有就报错 ⑦ NEVER 没事务 → 非事务执行 有事务 → 抛异常! 强制不能有事务,有就报错 📋 7种传播行为速查表 传播行为 当前有事务 当前无事务 典型场景 REQUIRED ⭐ 加入 新建 默认,大多数增删改 REQUIRES_NEW ⭐ 挂起+新建 新建 日志记录(不受业务回滚影响) NESTED 嵌套(Savepoint) 新建 子操作可独立回滚 SUPPORTS 加入 非事务 查询方法 NOT_SUPPORTED 挂起 非事务 不希望事务影响的操作 MANDATORY 加入 抛异常 必须被事务方法调用 NEVER 抛异常 非事务 不允许在事务中调用

图2:7种事务传播行为全景图 + 速查表

③ AOP 代理链图解

🎭 AOP 是什么?代理怎么工作?

AOP(面向切面编程)= 在不修改原有代码的前提下,给方法加"增强"。Spring 通过动态代理实现:JDK 代理(接口)和 CGLIB 代理(类)。

交叉路口装摄像头类比:
你开车通过一个路口(业务方法),路口前后装了摄像头(切面)。你不需要改变自己的驾驶方式,但摄像头自动记录了你的行踪(日志)、限速检查(权限)、事故处理(异常处理)。摄像头 = 切面,路口 = 切点,摄像头安装的位置 = 连接点。
AOP 代理链:多个切面的执行流程 调用方 🔧 代理对象(Proxy) 切面1:@Before 日志记录 → "方法开始执行..." ├─ 前置通知:在目标方法之前执行 切面2:@Around (前半) 性能计时开始 → startTime = now() ├─ 环绕通知:proceed() 之前的逻辑 🎯 目标方法:userService.createUser() 实际的业务逻辑在这里执行 切面2:@Around (后半) 性能计时结束 → log("耗时: " + elapsed + "ms") ├─ 环绕通知:proceed() 之后的逻辑 切面3:@AfterReturning 返回通知 → 记录返回值、发送事件 ├─ 方法正常返回后执行(可获取返回值) ⚠️ 异常时 → @AfterThrowing 执行 JDK代理:有接口 | CGLIB:无接口 Spring Boot 2.x 默认 CGLIB

图3:AOP 代理链 — 多个切面像洋葱一样包裹目标方法

通知类型执行时机能否获取返回值能否影响返回
@Before方法执行前
@Around包裹整个方法✅ 可替换返回值
@AfterReturning方法正常返回后
@AfterThrowing方法抛异常后✅ 获取异常
@After方法完成后(无论成败)

④ Spring MVC 请求流程图解

🌐 一个 HTTP 请求的完整旅程

从浏览器发出请求到返回响应,经过9个核心步骤。理解这个流程,面试中的 MVC 相关问题就能迎刃而解。

Spring MVC 请求处理全流程(9步) ① 客户端请求 GET /api/user/1 ② DispatcherServlet 前端控制器(核心) ③ HandlerMapping 查找 Handler ④ HandlerAdapter 适配执行 ⑤ Controller 方法 @GetMapping("/api/user/{id}") ⑥ Service 处理业务 返回 ModelAndView/数据 ⑦ ViewResolver 视图解析(模板渲染) @ResponseBody 跳过ViewResolver,直接序列化JSON ⑧⑨ 返回响应 DispatcherServlet → 客户端 🔑 核心组件职责 DispatcherServlet: 统一入口,协调所有组件 HandlerMapping: URL → Handler 映射 HandlerAdapter: 适配不同 Handler 执行 ViewResolver: 逻辑视图名 → 物理视图 HttpMessageConverter: 对象 ↔ JSON 序列化

图4:Spring MVC 请求处理全流程 — 9个核心步骤

⑤ Spring Boot 自动配置原理

🪄 "约定大于配置"背后的魔法

Spring Boot 引入 spring-boot-starter-* 后,相关 Bean 就自动装配好了。这是怎么做到的?核心是 @EnableAutoConfiguration → @Import(AutoConfigurationImportSelector) → META-INF/spring.factories

自动挡汽车类比:
手动挡(Spring)= 每次换挡都要自己踩离合、挂挡、松离合。
自动挡(Spring Boot)= 你只需要选 D 挡(引入 starter),变速箱自动帮你完成(自动配置)。
但你仍然可以手动干预(application.yml 覆盖默认值),就像切到 M 挡。
Spring Boot 自动配置全链路 @SpringBootApplication = @SpringBootConfig + @EnableAutoConfig + @ComponentScan @EnableAutoConfiguration AutoConfigurationImportSelector 读取 META-INF/spring.factories / spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 获取所有自动配置类的全类名列表 过滤:@Conditional 条件判断 @ConditionalOnClass / @ConditionalOnMissingBean / @ConditionalOnProperty ... ✅ 条件满足 → 装配 Bean 如:classpath 有 DataSource.class ❌ 条件不满足 → 跳过 如:已有自定义 Bean 或 classpath 缺类 💡 例:引入 spring-boot-starter-data-redis → RedisAutoConfiguration 检测到 RedisOperations.class → 自动创建 RedisTemplate Bean

图5:Spring Boot 自动配置全链路 — 从注解到 Bean 装配

条件注解作用示例
@ConditionalOnClassclasspath 中有指定类才装配有 DataSource → 装配数据源
@ConditionalOnMissingBean容器中没有该 Bean 才装配没有自定义 RedisTemplate → 用默认的
@ConditionalOnProperty配置文件中有指定属性才装配spring.cache.type=redis → 装配缓存
@ConditionalOnWebApplication当前是 Web 应用才装配Web 环境才装配 MVC
@ConditionalOnExpressionSpEL 表达式为 true 才装配复杂条件组合
自定义 Starter 的关键:① 创建 xxx-spring-boot-starter 模块 ② 编写自动配置类 + @Conditional 条件 ③ 在 META-INF/spring.factories 注册 ④ 其他项目引入 starter 即可自动装配。

⑥ @Autowired 注入原理图解

💉 Spring 是怎么把依赖"注入"进去的?

当你写 @Autowired,Spring 在创建 Bean 时会自动查找匹配的 Bean 并赋值。但"查找"的规则是什么?如果有多个匹配怎么办?

公司入职类比:
你入职了,工位上需要配一台电脑(依赖)。
byType = 按"电脑"这个类型去找 → 找到3台,不知道选哪个 → 报错!
byName = 按"小王的电脑"这个名字去找 → 精准匹配
@Primary = 默认配MacBook → 其他电脑都不考虑
@Qualifier = 指定要"ThinkPad" → 精准指定
@Autowired 注入查找流程 Step 1: 按类型查找(byType) 在容器中找所有 UserService 类型的 Bean → 只找到1个?直接注入 ✅ Step 2: 找到多个?按名称匹配(byName) 字段名或参数名 == Bean名?如字段名 userMasterService 匹配 Bean "userMasterService" ✅ Step 3a: 有@Qualifier?→ 按指定名找 @Qualifier("userSlaveService") → 精准匹配 ✅ Step 3b: 有@Primary?→ 优先选它 @Primary 标注的 Bean → 默认首选 ✅ ❌ 以上都不匹配?抛 NoUniqueBeanDefinitionException!
@Autowired 注入查找流程:byType → byName → @Qualifier/@Primary
AutowiredDemo.java
📁 src/main/java
📁 service
AutowiredDemo.java
UserService.java
OrderService.java
@Service public class OrderService { // ① 最简单:容器中只有一个 UserService → 直接注入 @Autowired private UserService userService; // ② 有多个实现时:用 @Qualifier 指定名字 @Autowired @Qualifier("userSlaveService") private UserService slaveService; // ③ 构造器注入(推荐!Spring官方推荐方式) private final PaymentService paymentService; @Autowired // Spring 4.3+ 单构造器可省略 public OrderService(PaymentService paymentService) { this.paymentService = paymentService; } // ④ setter注入 private LogService logService; @Autowired public void setLogService(LogService logService) { this.logService = logService; } } // ⑤ @Primary:默认首选 @Service @Primary // ← 容器中有多个UserService时,这个优先 public class UserMasterService implements UserService { ... } @Service public class UserSlaveService implements UserService { ... }
三种注入方式怎么选?
构造器注入(推荐✅)→ 不可变(final)、完全初始化、循环依赖直接暴露
Setter注入 → 可选依赖、可重新配置
字段注入(❌不推荐)→ 无法设final、测试难mock、隐藏依赖关系
Spring官方立场:自从Spring 4.3开始,强烈推荐构造器注入!

⑦ Bean 生命周期全链路图解

🥚 一个 Bean 从创建到销毁经历了什么?

面试必考题!Spring Bean 的生命周期可以简单概括为 4大阶段 × 多个扩展点。理解了扩展点,就知道 Spring 的"钩子"在哪里,定制化开发就有了方向。

公司入职类比:
🥚 实例化 = 发offer,人来了(构造器 new)
📦 属性赋值 = 配电脑、发工牌(@Autowired注入)
🔧 初始化 = 新人培训、签合同(@PostConstruct / afterPropertiesSet)
💀 销毁 = 离职交接、还电脑(@PreDestroy / destroy)

BeanPostProcessor = HR:每个人入职/离职时HR都要处理(前置/后置处理)
Spring Bean 生命周期全链路 ① 实例化 构造器 new Bean() ② 属性赋值 @Autowired注入 ③ 初始化 各种回调方法 ④ 就绪使用 Bean可被调用 ③ 初始化阶段的详细步骤(面试核心!) Step A: BeanPostProcessor.postProcessBeforeInitialization() → HR前置处理 Step B: @PostConstruct 方法执行 → 新人入职培训 Step C: InitializingBean.afterPropertiesSet() → 签合同 Step D: 自定义 init-method → 部门欢迎会 Step E: BeanPostProcessor.postProcessAfterInitialization() → HR后置处理 → AOP代理在此创建! ✅ Bean 就绪,放入 singletonObjects 一级缓存 💀 销毁:@PreDestroy → DisposableBean.destroy() → 自定义 destroy-method
Bean 生命周期4大阶段 + 初始化5步详细流程 + 销毁3步
BeanLifecycleDemo.java
📁 src/main/java
📁 service
AutowiredDemo.java
BeanLifecycleDemo.java
@Component public class BeanLifecycleDemo implements InitializingBean, DisposableBean, BeanNameAware { private String beanName; // Aware回调:获取Bean名称 @Override public void setBeanName(String name) { this.beanName = name; System.out.println("1. setBeanName: " + name); } // @PostConstruct:初始化(最先执行) @PostConstruct public void postConstruct() { System.out.println("2. @PostConstruct: 初始化逻辑"); } // InitializingBean接口方法(第二执行) @Override public void afterPropertiesSet() { System.out.println("3. afterPropertiesSet: 属性设置完毕"); } // 自定义init-method(第三执行) public void customInit() { System.out.println("4. customInit: 自定义初始化"); } // @PreDestroy:销毁(最先执行) @PreDestroy public void preDestroy() { System.out.println("5. @PreDestroy: 清理资源"); } // DisposableBean接口方法(第二执行) @Override public void destroy() { System.out.println("6. destroy: 释放连接"); } } // ⚠️ AOP代理在哪个阶段创建? // → BeanPostProcessor.postProcessAfterInitialization() 中! // → AbstractAutoProxyCreator.wrapIfNecessary() 创建代理 // → 所以代理对象是在初始化之后才替换原始对象的

❓ Spring深度面试FAQ

三级缓存为什么解决不了构造器循环依赖?
因为构造器循环依赖时,连"半成品"都创建不出来!三级缓存的第一步就是实例化(调用构造器),如果A构造器需要B,B构造器又需要A,两者都无法完成实例化,自然无法提前暴露到三级缓存中。

解决方案:① 改用setter注入代替构造器注入 ② 使用@Lazy延迟加载 ③ 重构设计,消除循环依赖
REQUIRED和REQUIRES_NEW的区别?
REQUIRED(默认):有事务就加入,没有就新建。方法A和方法B在同一个事务中,B抛异常A也回滚。

REQUIRES_NEW:总是新建独立事务。B有自己独立的事务,B回滚不影响A。但注意:A的事务会被挂起

典型场景:REQUIRES_NEW用于日志记录——业务方法回滚了,但日志不能丢!
@Transactional失效的常见场景?
5大失效场景
1️⃣ 同类方法调用:this.methodB() 不走代理 → 事务不生效(要用AopContext或注入自己)
2️⃣ 非public方法:@Transactional标注在private/protected方法上 → 不生效
3️⃣ 异常被吞:try-catch捕获了异常但没重新抛出 → 不回滚
4️⃣ rollbackFor没配:默认只回滚RuntimeException,checked异常不回滚 → 要配rollbackFor=Exception.class
5️⃣ 数据库引擎不支持:MyISAM不支持事务 → 用InnoDB!
@Autowired和@Resource的区别?
维度@Autowired@Resource
来源Spring框架JSR-250(Java标准)
查找方式先byType,再byName先byName,再byType
配合使用@Qualifier指定名称name属性指定名称
required属性@Autowired(required=false)不支持

推荐:Spring项目用@Autowired+@Qualifier;跨框架/标准化用@Resource。

BeanPostProcessor有什么用?
BeanPostProcessor = HR,每个Bean创建时都要经过它的手:

postProcessBeforeInitialization() → 初始化前处理(如解析注解)
postProcessAfterInitialization() → 初始化后处理(AOP代理在此创建!

常见实现
• AutowiredAnnotationBeanPostProcessor → 处理@Autowired注入
• CommonAnnotationBeanPostProcessor → 处理@PostConstruct/@PreDestroy
• AbstractAutoProxyCreator → 创建AOP代理

关键点:BeanPostProcessor是Spring扩展的灵魂,几乎所有"自动"功能都靠它实现。
AOP代理是什么时候创建的?
在BeanPostProcessor.postProcessAfterInitialization()中!

具体是 AbstractAutoProxyCreator 这个BeanPostProcessor,在Bean初始化完成后,检查是否需要创建代理(有没有匹配的切点),如果需要就用CGLIB或JDK动态代理创建代理对象,替换掉原始对象放入容器。

所以:三级缓存中存放的ObjectFactory λ,就是为了在循环依赖场景下也能提前创建代理(因为此时Bean还没走完初始化流程,不会触发postProcessAfterInitialization,但B需要拿到A的代理对象)。
JDK动态代理和CGLIB的区别?
维度JDK动态代理CGLIB代理
要求目标类必须实现接口目标类不能是final
原理实现接口, InvocationHandler继承子类,MethodInterceptor
性能JDK8+后性能已追平生成子类略慢,调用略快
Spring Boot默认✅ CGLIB(2.x起)

面试速答:JDK代理基于接口,CGLIB基于继承。Spring Boot 2.x默认CGLIB,不需要接口也能代理。

如何排查@Transactional不生效?
5步排查法
1️⃣ 检查是否public方法?
2️⃣ 检查是否同类调用?this.xxx()不走代理
3️⃣ 检查异常是否被吞?try-catch后没重新抛
4️⃣ 检查rollbackFor?默认只回滚RuntimeException
5️⃣ 检查数据库引擎?MyISAM不支持事务

同类调用的解决方案
① 注入自己:@Autowired private XxxService self; self.methodB();
② AopContext:((XxxService)AopContext.currentProxy()).methodB();(需@EnableAspectJAutoProxy(exposeProxy=true))
Spring Boot 2.6为什么默认禁止循环依赖?
Spring官方认为循环依赖是设计缺陷,三级缓存只是"掩盖"了问题,不是正确的解决方案。循环依赖意味着你的类职责划分有问题,应该:

① 提取公共逻辑到第三个Bean
② 用@Lazy延迟加载
③ 用setter注入代替构造器注入

如果升级2.6后报循环依赖错误,可以临时 spring.main.allow-circular-references=true 开启兼容,但应该尽早重构。
Spring事件机制是怎么回事?
Spring事件 = 寄快递
📦 事件(ApplicationEvent) = 快递包裹(发生了什么事)
📮 发布者(ApplicationEventPublisher) = 寄件人(发出事件)
📬 监听者(@EventListener) = 收件人(收到后处理)

好处:解耦!发布者不需要知道谁在监听,监听者可以随时增减。
典型场景:用户注册成功后 → 发邮件、发积分、发短信(这些不需要同步执行)
异步事件:@Async + @EventListener → 在独立线程中处理,不阻塞主流程

📋 本章知识速查卡

主题核心要点面试高频
循环依赖三级缓存singletonObjects→earlySingletonObjects→singletonFactories;不能解决构造器循环⭐⭐⭐⭐⭐
事务传播7种REQUIRED/REQUIRES_NEW/NESTED最常考;REQUIRED默认加入;REQUIRES_NEW独立事务⭐⭐⭐⭐⭐
AOP代理链洋葱模型;5种通知类型;@Around最强大;JDK代理vs CGLIB⭐⭐⭐⭐
MVC请求流程9步流程;DispatcherServlet是核心;@ResponseBody跳过ViewResolver⭐⭐⭐⭐
自动配置原理@EnableAutoConfig→spring.factories→@Conditional过滤→Bean装配⭐⭐⭐⭐⭐
@Autowired注入byType→byName→@Qualifier/@Primary;构造器注入推荐⭐⭐⭐⭐
Bean生命周期实例化→属性赋值→初始化5步→就绪→销毁3步;AOP在postProcessAfterInitialization⭐⭐⭐⭐⭐
核心三原则速记

Spring三原则:① Bean默认单例,不要在Bean中存请求级状态 → ② @Transactional不能在类内部调用中生效 → ③ 循环依赖能用但不推荐