← 返回 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
// 一级缓存:完整的 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 装配
条件注解 作用 示例
@ConditionalOnClass classpath 中有指定类才装配 有 DataSource → 装配数据源
@ConditionalOnMissingBean 容器中没有该 Bean 才装配 没有自定义 RedisTemplate → 用默认的
@ConditionalOnProperty 配置文件中有指定属性才装配 spring.cache.type=redis → 装配缓存
@ConditionalOnWebApplication 当前是 Web 应用才装配 Web 环境才装配 MVC
@ConditionalOnExpression SpEL 表达式为 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
@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
@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不能在类内部调用中生效 → ③ 循环依赖能用但不推荐