Spring核心原理全解析:从零到生产实战

Spring Framework 全面专题详解|从 0 基础到源码、生产实战与 Java 后端面试

定位:这不是一份"Spring 八股速记",而是一份面向 Java 后端开发者的系统学习资料。目标不是让你记住几个术语,而是让你真正理解 Spring 为什么这样设计、对象怎样进入容器、注解什么时候生效、代理对象什么时候产生、事务为什么会失效,以及生产环境出现问题时应该从哪里排查。

统一讲解模板:

是什么 → 为什么需要 → 专业术语解释 → 底层原理 → 核心源码类/方法 → 简化源码解读 → 完整 Java/配置示例 → 真实业务场景 → 适合场景 → 不适合场景/局限 → 常见坑 → 生产注意事项 → 故障排查 → 面试复盘

版本说明(2026-10):

  • Spring Framework 官方稳定版本:7.0.9
  • 同时维护的 6.x 稳定线:6.2.19
  • Spring Framework 6.0+ 要求 Java 17+
  • Spring 6+/7 使用现代 Jakarta (jakarta.*) 命名空间
  • 本文核心原理对 Spring 5/6/7 大量场景都成立;涉及内部源码类或 API 差异时以实际项目版本为准

阅读原则:不要一开始硬背源码行号。先把"为什么"和"调用链"讲清楚,再去看源码具体实现,会容易很多。


总目录

  1. [一、Spring Framework 到底是什么](#一、Spring Framework 到底是什么)
  2. [二、Spring 核心模块与整个知识体系](#二、Spring 核心模块与整个知识体系)
  3. [三、IoC、DI 与"控制反转"到底反转了什么](#三、IoC、DI 与“控制反转”到底反转了什么)
  4. [四、BeanDefinition:Bean 的"说明书"](#四、BeanDefinition:Bean 的“说明书”)
  5. [五、Bean 注册:一个 @Service 是怎样进入容器的](#五、Bean 注册:一个 @Service 是怎样进入容器的)
  6. [六、BeanFactory 与 ApplicationContext](#六、BeanFactory 与 ApplicationContext)
  7. [七、ApplicationContext.refresh() 容器启动源码主线](#七、ApplicationContext.refresh() 容器启动源码主线)
  8. [八、Bean 实例化:Spring 怎样真正创建对象](#八、Bean 实例化:Spring 怎样真正创建对象)
  9. [九、依赖注入:@Autowired 到底怎么找到对象](#九、依赖注入:@Autowired 到底怎么找到对象)
  10. [十、Bean 生命周期完整流程](#十、Bean 生命周期完整流程)
  11. 十一、Aware、初始化与销毁回调怎么选
  12. 十二、BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor、BeanPostProcessor
  13. 十三、FactoryBean:为什么接口没有实现类也能注入代理对象
  14. [十四、Bean Scope 与单例 Bean 线程安全](#十四、Bean Scope 与单例 Bean 线程安全)
  15. 十五、循环依赖与三级缓存
  16. [十六、AOP 核心概念](#十六、AOP 核心概念)
  17. [十七、Spring AOP 代理创建源码主线](#十七、Spring AOP 代理创建源码主线)
  18. [十八、JDK 动态代理与 CGLIB](#十八、JDK 动态代理与 CGLIB)
  19. [十九、Advice、Advisor、Pointcut 与拦截器链](#十九、Advice、Advisor、Pointcut 与拦截器链)
  20. [二十、AOP 自调用失效与生产坑](#二十、AOP 自调用失效与生产坑)
  21. [二十一、Spring 声明式事务完整原理](#二十一、Spring 声明式事务完整原理)
  22. [二十二、TransactionManager、TransactionInterceptor 与线程资源绑定](#二十二、TransactionManager、TransactionInterceptor 与线程资源绑定)
  23. 二十三、事务传播行为
  24. 二十四、事务隔离级别、readOnly、timeout、rollbackFor
  25. [二十五、@Transactional 失效场景](#二十五、@Transactional 失效场景)
  26. [二十六、Spring MVC 整体架构与请求链路](#二十六、Spring MVC 整体架构与请求链路)
  27. 二十七、DispatcherServlet、HandlerMapping、HandlerAdapter
  28. [二十八、参数解析、数据绑定与 HttpMessageConverter](#二十八、参数解析、数据绑定与 HttpMessageConverter)
  29. [二十九、Validation 参数校验](#二十九、Validation 参数校验)
  30. 三十、全局异常处理
  31. [三十一、Filter、Interceptor、AOP 如何选](#三十一、Filter、Interceptor、AOP 如何选)
  32. [三十二、Spring Event 事件机制](#三十二、Spring Event 事件机制)
  33. [三十三、Resource 资源抽象](#三十三、Resource 资源抽象)
  34. 三十四、ConversionService、Formatter、DataBinder
  35. [三十五、SpEL 表达式语言](#三十五、SpEL 表达式语言)
  36. [三十六、JdbcTemplate 与 Spring JDBC](#三十六、JdbcTemplate 与 Spring JDBC)
  37. 三十七、数据访问异常统一转换
  38. [三十八、REST Client、WebClient 与 HTTP 调用](#三十八、REST Client、WebClient 与 HTTP 调用)
  39. 三十九、异步任务、定时任务与线程池
  40. [四十、Spring Cache](#四十、Spring Cache)
  41. [四十一、Spring Test 测试体系](#四十一、Spring Test 测试体系)
  42. [四十二、AOT 与现代 Spring](#四十二、AOT 与现代 Spring)
  43. [四十三、Spring 中常见设计模式](#四十三、Spring 中常见设计模式)
  44. 四十四、生产环境性能问题与故障排查
  45. [四十五、Spring 源码阅读路线](#四十五、Spring 源码阅读路线)
  46. 四十六、高频面试题与连续追问
  47. [四十七、一张主线串起整个 Spring](#四十七、一张主线串起整个 Spring)

一、Spring Framework 到底是什么

1.1 是什么

Spring Framework 是 Java 企业级开发领域最核心的基础框架之一。

如果只用一句话概括:

text 复制代码
Spring 是一个"对象管理容器 + 基础设施框架"。

"对象管理容器"主要指:

text 复制代码
IoC / DI
Bean 生命周期
Bean 依赖关系
Bean 扩展机制

"基础设施框架"则包括:

text 复制代码
AOP
事务
MVC
数据访问
事件
资源管理
类型转换
校验
表达式语言
异步
定时
缓存
测试
HTTP 客户端
AOT

1.2 为什么 Java 项目需要 Spring

假设不用 Spring:

java 复制代码
public class OrderService {

    private final UserService userService =
            new UserService();

    private final PaymentService paymentService =
            new PaymentService();

    public void createOrder() {
        // ...
    }
}

问题看起来不大,但系统一复杂就会出现:

text 复制代码
对象创建散落在各个类
类和具体实现强耦合
对象很难统一替换
测试时难注入 Mock
事务代码到处重复
日志/权限/审计到处复制
生命周期无法统一管理

Spring 把思路改为:

text 复制代码
对象不要由业务类自己 new
↓
对象交给容器创建
↓
依赖关系交给容器装配
↓
公共基础能力通过代理和扩展点织入

业务类变成:

java 复制代码
@Service
public class OrderService {

    private final UserService userService;
    private final PaymentService paymentService;

    public OrderService(
            UserService userService,
            PaymentService paymentService) {

        this.userService = userService;
        this.paymentService = paymentService;
    }
}

此时:

text 复制代码
OrderService 只关心:
"我要 UserService 和 PaymentService"

而不关心:
"它们怎么创建、生命周期多久、有没有代理"

1.3 Spring 最重要的两条主线

IoC

解决:

text 复制代码
对象由谁创建
对象如何管理
对象之间如何建立依赖

AOP

解决:

text 复制代码
如何在不污染业务代码的情况下
统一增加事务、日志、权限、监控等横切能力

Spring 的声明式事务,本质上就是 AOP 的典型应用。


1.4 Spring 不等于 Spring Boot

一定区分:

text 复制代码
Spring Framework
= 核心能力

Spring Boot
= 基于 Spring 的自动配置、工程化、运行和生产能力

例如:

text 复制代码
BeanFactory
AOP
@Transactional
DispatcherServlet

属于 Spring Framework 核心体系。

而:

text 复制代码
@SpringBootApplication
AutoConfiguration
Starter
Actuator

主要属于 Spring Boot。


1.5 适合场景

Spring 非常适合:

  • 企业后台系统
  • REST API
  • 微服务底层应用
  • 工作流系统
  • 审批系统
  • 电商/支付/订单
  • 数据平台
  • MQ 消费应用
  • 集成平台

1.6 不适合/局限

Spring 的代价包括:

text 复制代码
抽象层多
Bean 生命周期复杂
代理调用链较深
内部扩展点多
不理解原理时问题不好定位

一个只有几十行代码的简单 CLI 工具,未必需要完整 Spring 容器。


1.7 面试复盘

Q:Spring 最核心的价值是什么?

推荐回答:

Spring 最核心的能力是 IoC 和 AOP。IoC 把对象创建、生命周期和依赖关系交给容器管理,降低业务类之间的直接耦合;AOP 通过代理和拦截器链实现事务、日志、权限等横切能力,让业务代码关注业务本身。在此基础上,Spring 又提供事务、MVC、数据访问、事件、校验等完整企业级基础设施。


[⬆ 返回总目录](#⬆ 返回总目录)

二、Spring 核心模块与整个知识体系

Spring Framework 不只是:

text 复制代码
IoC + AOP + MVC

当前完整体系大致可以按以下维度理解。

2.1 Core Container

包括:

text 复制代码
spring-core
spring-beans
spring-context
spring-expression

负责:

text 复制代码
IoC
BeanFactory
ApplicationContext
资源
事件
类型转换
SpEL

2.2 AOP

主要:

text 复制代码
spring-aop

解决:

text 复制代码
代理
Pointcut
Advice
Advisor
Interceptor Chain

2.3 Data Access

包括:

text 复制代码
事务
Spring JDBC
ORM 集成
R2DBC

2.4 Web

包括:

text 复制代码
Spring MVC
WebFlux
WebSocket
REST Client
WebClient

2.5 Integration

包括:

text 复制代码
Task
Scheduling
Cache
JMS
邮件
Observability

2.6 Testing

包括:

text 复制代码
Spring TestContext
MockMvc
WebTestClient
事务测试
Context 缓存

2.7 最重要的学习顺序

推荐:

text 复制代码
IoC/Bean
↓
Bean 生命周期
↓
扩展点
↓
循环依赖
↓
AOP/代理
↓
事务
↓
Spring MVC
↓
数据访问
↓
其他集成能力

为什么:

text 复制代码
事务依赖 AOP
AOP 依赖 Bean 生命周期
Bean 生命周期依赖 IoC
MVC Controller 本质也是 Bean

所以必须先学"容器"。


[⬆ 返回总目录](#⬆ 返回总目录)

三、IoC、DI 与"控制反转"到底反转了什么

3.1 IoC 是什么

IoC:

text 复制代码
Inversion of Control
控制反转

这里的"控制"主要指:

text 复制代码
对象创建权
依赖选择权
生命周期管理权

传统:

java 复制代码
PaymentService payment =
        new AliPayService();

业务代码决定:

text 复制代码
创建谁
什么时候创建
创建几份

Spring:

java 复制代码
@Service
public class OrderService {

    private final PaymentService paymentService;

    public OrderService(
            PaymentService paymentService) {

        this.paymentService = paymentService;
    }
}

业务类只声明:

text 复制代码
我需要 PaymentService

到底注入:

text 复制代码
AliPayService
WechatPayService
MockPaymentService

由容器装配。


3.2 为什么叫"反转"

以前:

text 复制代码
对象主动寻找依赖

现在:

text 复制代码
容器主动提供依赖

因此控制方向反转。


3.3 DI 是什么

DI:

text 复制代码
Dependency Injection
依赖注入

它是 IoC 思想最典型的实现方式。

IoC 是理念:

text 复制代码
把控制权交给容器

DI 是动作:

text 复制代码
容器把依赖注入对象

3.4 三种常见注入

构造器注入

java 复制代码
@Service
public class OrderService {

    private final PaymentService paymentService;

    public OrderService(
            PaymentService paymentService) {

        this.paymentService = paymentService;
    }
}

Setter 注入

java 复制代码
@Autowired
public void setPaymentService(
        PaymentService paymentService) {

    this.paymentService = paymentService;
}

字段注入

java 复制代码
@Autowired
private PaymentService paymentService;

3.5 为什么推荐构造器注入

因为依赖:

text 复制代码
显式
不可变
容易测试
容易发现循环依赖

例如:

java 复制代码
OrderService service =
        new OrderService(mockPaymentService);

普通单元测试不需要反射去设置字段。


3.6 构造器参数太多说明什么

如果:

java 复制代码
public OrderService(
    A a, B b, C c, D d, E e,
    F f, G g, H h, I i, J j) {
}

不要第一反应:

text 复制代码
"Spring 注入真麻烦"

更可能是:

text 复制代码
这个类职责太多
需要拆分

构造器注入会把设计问题暴露出来。


3.7 字段注入为什么不推荐

字段注入方便:

java 复制代码
@Autowired
private UserService userService;

但问题:

text 复制代码
依赖隐藏
字段可变
单元测试不方便
容易形成巨型 Service
脱离容器不好实例化

并不是说:

text 复制代码
字段注入一定不能工作

而是工程可维护性较差。


3.8 实际业务场景

支付系统:

java 复制代码
public interface PaymentGateway {
    void pay(Order order);
}

实现:

java 复制代码
@Component
public class AliPaymentGateway
        implements PaymentGateway {
}

业务:

java 复制代码
@Service
public class PaymentApplicationService {

    private final PaymentGateway gateway;

    public PaymentApplicationService(
            PaymentGateway gateway) {
        this.gateway = gateway;
    }
}

这样 Service 不关心:

text 复制代码
Gateway 具体如何创建

3.9 适合场景

IoC/DI 几乎适合所有:

text 复制代码
业务 Service
Repository
Controller
Client
策略实现
基础设施组件

3.10 不适合什么

不要把所有普通 DTO、VO、Entity 都注册成 Bean。

例如:

java 复制代码
@Component
public class CreateOrderRequest {
}

通常没有意义。

请求 DTO 应该:

text 复制代码
按请求创建
不是容器级业务组件

[⬆ 返回总目录](#⬆ 返回总目录)

四、BeanDefinition:Bean 的"说明书"

4.1 BeanDefinition 是什么

很多人以为 Spring 扫到:

java 复制代码
@Service
public class UserService {}

之后马上:

java 复制代码
new UserService();

实际上核心思路是:

text 复制代码
先把"怎么创建这个 Bean"记录下来

这个元数据对象就是:

text 复制代码
BeanDefinition

可以理解成:

text 复制代码
BeanDefinition = Bean 的施工图
Bean = 根据施工图创建出来的实例

4.2 BeanDefinition 记录什么

典型包括:

text 复制代码
Bean class
Scope
Lazy
Primary
DependsOn
构造器信息
属性值
Factory Method
Init Method
Destroy Method
Role
Autowire Candidate

4.3 为什么不能扫描后立即创建 Bean

因为 Spring 需要给扩展机制机会:

text 复制代码
扫描
↓
BeanDefinition 注册
↓
BeanDefinition 后置处理
↓
修改元数据
↓
再创建 Bean

例如:

text 复制代码
@Configuration 解析
@Mapper 动态注册
框架 Starter 动态 BeanDefinition

都需要在实例化之前操作元数据。


4.4 常见实现

例如:

text 复制代码
RootBeanDefinition
GenericBeanDefinition
AnnotatedGenericBeanDefinition
ScannedGenericBeanDefinition

学习时不要求一开始死背区别。

重点:

text 复制代码
都在描述"Bean 如何被创建"

4.5 BeanDefinitionRegistry

负责:

text 复制代码
注册 BeanDefinition

典型核心容器:

text 复制代码
DefaultListableBeanFactory

内部可以粗略理解为:

java 复制代码
Map<String, BeanDefinition>

真实实现还有顺序、别名、类型缓存等更多结构。


4.6 手动注册示例

java 复制代码
DefaultListableBeanFactory factory =
        new DefaultListableBeanFactory();

GenericBeanDefinition bd =
        new GenericBeanDefinition();

bd.setBeanClass(UserService.class);
bd.setScope("singleton");

factory.registerBeanDefinition(
        "userService",
        bd
);

UserService userService =
        factory.getBean(UserService.class);

这就是最基础的:

text 复制代码
定义
→ 注册
→ 获取

4.7 实际框架应用

MyBatis:

text 复制代码
Mapper 接口

并没有实现类,但框架可以:

text 复制代码
扫描 Mapper
↓
动态注册特殊 BeanDefinition / FactoryBean
↓
最终得到代理对象

Feign、RPC、SDK Starter 都有类似思想。


4.8 常见异常

NoSuchBeanDefinitionException

text 复制代码
需要的 BeanDefinition/Bean 不存在

NoUniqueBeanDefinitionException

text 复制代码
同类型候选太多
无法唯一确定

BeanDefinitionOverrideException

text 复制代码
重复定义发生冲突

4.9 面试复盘

Q:BeanDefinition 和 Bean 有什么区别?

BeanDefinition 是 Bean 的元数据描述,记录类型、Scope、初始化方式、构造信息等;真正的 Bean 实例会在后续创建阶段根据 BeanDefinition 实例化和初始化。


[⬆ 返回总目录](#⬆ 返回总目录)

五、Bean 注册:一个 @Service 是怎样进入容器的

5.1 先看表面

java 复制代码
@Service
public class OrderService {
}

为什么 Spring 能知道它存在?

因为通常配置了:

java 复制代码
@ComponentScan

或者 Spring Boot 主类间接启用了组件扫描。


5.2 扫描流程

简化:

text 复制代码
@ComponentScan
↓
ClassPathBeanDefinitionScanner
↓
扫描 classpath
↓
读取类元数据
↓
判断是否是候选组件
↓
生成 BeanDefinition
↓
BeanDefinitionRegistry

5.3 为什么扫描时不一定要创建 Class 实例

Spring 可以读取:

text 复制代码
.class 元数据

分析:

text 复制代码
注解
类名
接口
修饰符

不用先:

java 复制代码
new

对象。


5.4 @Component、@Service、@Repository、@Controller

它们都可参与组件扫描。

区别主要在:

text 复制代码
语义
某些额外框架行为

例如:

java 复制代码
@Repository

不仅表达"数据访问层",还与 Spring 的数据访问异常转换体系有关。


5.5 @Bean 注册

第三方对象不能改源码:

java 复制代码
public class ThirdPartyClient {
}

不能给它加:

java 复制代码
@Component

可以:

java 复制代码
@Configuration
public class ClientConfig {

    @Bean
    public ThirdPartyClient client() {
        return new ThirdPartyClient();
    }
}

5.6 @Import

java 复制代码
@Import(MyConfiguration.class)

可以导入:

text 复制代码
配置类
ImportSelector
ImportBeanDefinitionRegistrar

它是很多框架扩展能力的重要入口。


5.7 ImportSelector

适合:

text 复制代码
根据条件动态返回一批需要导入的配置类名

Spring Boot 自动配置就大量利用类似机制。


5.8 ImportBeanDefinitionRegistrar

更底层:

text 复制代码
直接拿 BeanDefinitionRegistry
动态注册 BeanDefinition

适合:

text 复制代码
RPC Client
Mapper
动态接口代理
框架基础设施

5.9 不要滥用动态注册

普通业务代码如果只是:

text 复制代码
创建一个 Service

直接:

java 复制代码
@Service

最清楚。

不要为了"高级"写:

text 复制代码
ImportBeanDefinitionRegistrar

否则可读性极差。


[⬆ 返回总目录](#⬆ 返回总目录)

六、BeanFactory 与 ApplicationContext

6.1 BeanFactory 是什么

BeanFactory 是 Spring IoC 最基础的容器接口。

核心能力:

text 复制代码
getBean
containsBean
getType
isSingleton

它回答:

text 复制代码
"这个 Bean 有没有?"
"给我这个 Bean"

6.2 ApplicationContext 是什么

ApplicationContext 是更完整的应用级容器。

除了 Bean 管理,还提供:

text 复制代码
事件
资源
国际化
环境
自动发现和注册各种处理器
Web 集成

6.3 关系不要简单说成"继承"

ApplicationContext 接口体系继承 BeanFactory 相关能力,同时具体实现内部也组合/使用底层 BeanFactory。

理解上可以记:

text 复制代码
BeanFactory = IoC 核心发动机
ApplicationContext = 完整应用容器

6.4 常见实现

text 复制代码
AnnotationConfigApplicationContext
ClassPathXmlApplicationContext
GenericApplicationContext

Web 环境还有对应 WebApplicationContext。


6.5 "BeanFactory 懒加载,ApplicationContext 饿加载"准确吗

不够准确。

真正是否提前创建取决于:

text 复制代码
Bean Scope
lazy-init
容器启动过程
preInstantiateSingletons

典型 ApplicationContext 在 refresh 末期会创建非懒加载单例 Bean,但这不意味着所有 Bean 都一定立即实例化。


6.6 业务场景

普通业务项目:

text 复制代码
几乎都使用 ApplicationContext

BeanFactory 更多出现在:

text 复制代码
Spring 源码
框架底层
高级扩展

[⬆ 返回总目录](#⬆ 返回总目录)

七、ApplicationContext.refresh() 容器启动源码主线

这是 Spring 源码最重要的方法之一。

如果你把 refresh() 理顺,很多八股题会自动串起来。


7.1 为什么叫 refresh

它并不只是:

text 复制代码
刷新页面

它代表:

text 复制代码
准备或刷新一个完整 ApplicationContext

包括:

text 复制代码
BeanFactory
后置处理器
监听器
单例 Bean
事件系统

7.2 典型入口

java 复制代码
AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(
                AppConfig.class
        );

最终会走到:

text 复制代码
AbstractApplicationContext.refresh()

7.3 关键主线

简化:

text 复制代码
refresh()
│
├─ prepareRefresh()
├─ obtainFreshBeanFactory()
├─ prepareBeanFactory()
├─ postProcessBeanFactory()
├─ invokeBeanFactoryPostProcessors()
├─ registerBeanPostProcessors()
├─ initMessageSource()
├─ initApplicationEventMulticaster()
├─ onRefresh()
├─ registerListeners()
├─ finishBeanFactoryInitialization()
└─ finishRefresh()

不同版本具体细节会演进,但主线非常稳定。


7.4 prepareRefresh()

主要做:

text 复制代码
容器状态初始化
Environment 校验
准备 early events

可以理解:

text 复制代码
"开始启动了,先把上下文状态准备好"

7.5 obtainFreshBeanFactory()

获取/准备:

text 复制代码
真正负责 Bean 管理的 BeanFactory

对某些 Context:

text 复制代码
可能创建新的 BeanFactory

7.6 prepareBeanFactory()

给 BeanFactory 注册:

text 复制代码
ClassLoader
BeanExpressionResolver
PropertyEditor
ApplicationContextAwareProcessor
一些特殊依赖
环境 Bean

这一步把 BeanFactory 和完整 ApplicationContext 能力连接起来。


7.7 invokeBeanFactoryPostProcessors()

非常关键。

此时:

text 复制代码
大部分普通 Bean 还没创建

但 BeanDefinition 已经有很多了。

所以它允许:

text 复制代码
修改 BeanDefinition
继续注册 BeanDefinition

例如:

text 复制代码
@Configuration 类解析
@ComponentScan
@Bean 方法解析

都与这一阶段的重要处理器有关。


7.8 ConfigurationClassPostProcessor

这是注解配置体系极其重要的处理器。

它会解析:

text 复制代码
@Configuration
@ComponentScan
@Import
@ImportResource
@Bean

所以:

java 复制代码
@Configuration
@ComponentScan("com.demo")
public class AppConfig {
}

不是启动前就全部展开完毕。

容器启动时会进一步处理。


7.9 registerBeanPostProcessors()

这一阶段把:

text 复制代码
BeanPostProcessor

注册进 BeanFactory。

为什么必须先注册?

因为后面 Bean 创建时:

text 复制代码
@Autowired
@PostConstruct
AOP

等很多能力都需要各种 BeanPostProcessor 参与。


7.10 initApplicationEventMulticaster()

准备:

text 复制代码
Spring 事件广播器

后面 publishEvent() 就需要它。


7.11 registerListeners()

把监听器注册到事件系统。


7.12 finishBeanFactoryInitialization()

非常重要。

典型工作:

text 复制代码
准备 ConversionService
嵌入值解析
初始化 LoadTimeWeaver 相关
冻结配置
preInstantiateSingletons

其中:

text 复制代码
preInstantiateSingletons()

会触发大部分非懒加载单例 Bean 的创建。


7.13 finishRefresh()

完成:

text 复制代码
生命周期处理
发布 ContextRefreshedEvent 等

说明:

text 复制代码
容器基本可用了

7.14 为什么理解 refresh 对面试有用

因为可以回答:

text 复制代码
BeanFactoryPostProcessor 什么时候执行?
BeanPostProcessor 什么时候注册?
单例 Bean 什么时候创建?
事件机制什么时候准备?
@ComponentScan 在哪里被进一步解析?

7.15 生产注意事项

如果应用启动很慢,refresh() 期间重点排查:

text 复制代码
Bean 构造器
BeanPostProcessor
@PostConstruct
外部连接
Configuration 解析
大范围扫描
非懒加载单例

[⬆ 返回总目录](#⬆ 返回总目录)

八、Bean 实例化:Spring 怎样真正创建对象

8.1 getBean 并不是简单 Map.get

调用:

java 复制代码
context.getBean("orderService")

可能:

text 复制代码
单例已经存在 → 直接返回

也可能:

text 复制代码
第一次获取 → 触发完整创建流程

8.2 核心调用链

学习主线:

text 复制代码
getBean()
↓
doGetBean()
↓
getSingleton()
↓
createBean()
↓
doCreateBean()
↓
createBeanInstance()
↓
populateBean()
↓
initializeBean()
↓
addSingleton()

内部细节更多,但先掌握这条。


8.3 doGetBean()

大致负责:

text 复制代码
先查缓存
处理别名
处理 parent BeanFactory
检查 depends-on
获取 BeanDefinition
根据 scope 创建

8.4 createBean()

创建前还可能:

text 复制代码
让 InstantiationAwareBeanPostProcessor 提前返回代理对象

等高级扩展。


8.5 doCreateBean()

这是 Bean 创建核心。

可以粗略分成三阶段:

text 复制代码
实例化
↓
属性填充
↓
初始化

同时循环依赖的:

text 复制代码
early exposure

也主要发生在这里。


8.6 createBeanInstance()

决定怎么实例化:

text 复制代码
构造器
Factory Method
Supplier

不是所有 Bean 都直接:

java 复制代码
clazz.getDeclaredConstructor().newInstance()

8.7 populateBean()

负责:

text 复制代码
依赖注入
属性赋值

@Autowired 相关处理会在这个阶段参与。


8.8 initializeBean()

负责:

text 复制代码
Aware
初始化前 BPP
初始化方法
初始化后 BPP

AOP 代理通常与:

text 复制代码
初始化后处理

密切相关。


8.9 为什么创建一个 Bean 会"顺便"创建很多 Bean

例如:

text 复制代码
OrderService
→ UserService
→ UserRepository
→ DataSource

构造/字段注入时:

text 复制代码
容器发现依赖没创建
→ 递归 getBean()

因此一个 Bean 的创建形成:

text 复制代码
依赖图递归解析

[⬆ 返回总目录](#⬆ 返回总目录)

九、依赖注入:@Autowired 到底怎么找到对象

9.1 @Autowired 谁处理

核心后置处理器:

text 复制代码
AutowiredAnnotationBeanPostProcessor

它本身是一个非常重要的 BeanPostProcessor 扩展。


9.2 什么时候扫描 @Autowired

Bean 创建过程中,会寻找:

text 复制代码
构造器
字段
方法

上的注入元数据。

然后在属性填充阶段:

text 复制代码
解析依赖

9.3 依赖解析核心思路

假设:

java 复制代码
@Autowired
private PaymentService paymentService;

容器需要回答:

text 复制代码
1. PaymentService 类型有哪些 Bean?
2. 有几个候选?
3. 哪些是 autowireCandidate?
4. 有没有 @Primary?
5. 有没有 @Qualifier?
6. 名称是否匹配?
7. 最终选谁?

9.4 核心源码角色

理解这些名字:

text 复制代码
DependencyDescriptor
DefaultListableBeanFactory
doResolveDependency()
findAutowireCandidates()
determineAutowireCandidate()

9.5 多实现示例

java 复制代码
public interface PayService {
    void pay();
}
java 复制代码
@Service
public class AliPayService
        implements PayService {
}
java 复制代码
@Service
public class WechatPayService
        implements PayService {
}

注入:

java 复制代码
@Autowired
private PayService payService;

此时候选:

text 复制代码
aliPayService
wechatPayService

Spring 无法唯一选择,通常报:

text 复制代码
NoUniqueBeanDefinitionException

9.6 @Primary

java 复制代码
@Primary
@Service
public class AliPayService
        implements PayService {
}

表示:

text 复制代码
同类型候选多个时优先考虑我

9.7 @Qualifier

java 复制代码
public OrderService(
    @Qualifier("wechatPayService")
    PayService payService) {
}

显式缩小候选范围。


9.8 List 注入

Spring 支持:

java 复制代码
public PayManager(
        List<PayService> payServices) {
}

把同类型多个实现全部注入。

这是策略模式非常实用的方式。


9.9 Map 注入

例如:

java 复制代码
public PayManager(
        Map<String, PayService> payServiceMap) {
}

Key 通常是 Bean name。

可以用于:

text 复制代码
动态策略路由

9.10 ObjectProvider

如果依赖:

text 复制代码
可选
延迟获取
可能多实例

可以考虑:

java 复制代码
ObjectProvider<MyService>

比直接:

text 复制代码
ApplicationContext.getBean()

更符合依赖注入风格。


9.11 适合策略模式的业务场景

支付渠道:

text 复制代码
ALI
WECHAT
UNION

可以:

java 复制代码
Map<String, PayStrategy>

统一注册策略。

不要写巨大的:

java 复制代码
if ("ALI") ...
else if ("WECHAT") ...

9.12 常见坑

类型太宽

java 复制代码
@Autowired
private Object service;

没有明确意义。

大量字符串 Qualifier

如果到处:

text 复制代码
"aliPayService"

硬编码,后期重命名风险高。

可选依赖写成必选

某个插件不存在时:

text 复制代码
整个应用启动失败

应根据设计使用 Optional/ObjectProvider/条件配置。

[⬆ 返回总目录](#⬆ 返回总目录)

十、Bean 生命周期完整流程

Bean 生命周期是 Spring 面试中最核心、也最容易被"背顺序"带偏的知识点之一。

真正应该理解的是:

text 复制代码
Spring 为什么需要这么多生命周期阶段?

答案是:

text 复制代码
因为框架需要在"对象从无到有"的过程中
不断插入扩展逻辑

例如:

text 复制代码
依赖注入
Aware 回调
@PostConstruct
AOP 代理
事务代理
销毁资源

10.1 宏观流程

先记住主线:

text 复制代码
BeanDefinition
↓
实例化
↓
提前暴露(特定循环依赖场景)
↓
属性填充 / 依赖注入
↓
Aware 回调
↓
BeanPostProcessor Before
↓
@PostConstruct
↓
InitializingBean
↓
自定义 init-method
↓
BeanPostProcessor After
↓
可能得到代理对象
↓
Bean 可使用
↓
@PreDestroy
↓
DisposableBean
↓
自定义 destroy-method

10.2 第一步:实例化

实例化:

text 复制代码
只是把 Java 对象创建出来

类似:

java 复制代码
new UserService()

但此时:

text 复制代码
@Autowired 字段可能还没有值

所以:

text 复制代码
实例化 ≠ 初始化完成

10.3 第二步:属性填充

populateBean() 阶段会进行:

text 复制代码
依赖注入
属性赋值

例如:

java 复制代码
@Autowired
private UserRepository repository;

会在这里被解析和注入。


10.4 第三步:Aware 回调

Spring 提供一些 Aware 接口,让 Bean 可以感知容器基础设施。

例如:

text 复制代码
BeanNameAware
BeanFactoryAware
ApplicationContextAware
EnvironmentAware
ResourceLoaderAware

例子:

java 复制代码
@Component
public class DemoBean
        implements BeanNameAware {

    @Override
    public void setBeanName(
            String name) {

        System.out.println(
                "beanName = " + name
        );
    }
}

10.5 为什么不推荐业务代码大量实现 Aware

因为会增加:

text 复制代码
业务对象对 Spring 容器 API 的耦合

普通业务依赖:

text 复制代码
直接构造器注入

通常更清晰。

Aware 更适合:

text 复制代码
框架
基础组件
容器扩展

10.6 BeanPostProcessor Before

所有已注册的 BeanPostProcessor 可以在初始化前参与。

简化:

java 复制代码
for (BeanPostProcessor processor : processors) {
    bean =
        processor
            .postProcessBeforeInitialization(
                bean,
                beanName
            );
}

10.7 @PostConstruct

现代 Spring 使用 Jakarta 注解:

java 复制代码
@PostConstruct
public void init() {
}

它在:

text 复制代码
依赖注入完成之后

执行。

所以适合:

text 复制代码
依赖已经可用后
做轻量初始化

10.8 InitializingBean

java 复制代码
public class UserService
        implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
    }
}

优点:

text 复制代码
明确

缺点:

text 复制代码
业务类依赖 Spring 接口

普通业务更常用 @PostConstruct 或配置初始化方法。


10.9 init-method

Java 配置:

java 复制代码
@Bean(
    initMethod = "init"
)
public Client client() {
    return new Client();
}

适合:

text 复制代码
第三方类不能加 @PostConstruct

10.10 BeanPostProcessor After

初始化后处理器:

text 复制代码
可能直接返回原对象
也可能返回包装/代理对象

AOP 自动代理创建器通常会在这个阶段判断:

text 复制代码
这个 Bean 是否需要代理

因此:

text 复制代码
容器最终保存和注入的对象
可能已经不是原始 target
而是 proxy

10.11 销毁阶段

单例 Bean 在 Context 关闭时可能调用:

text 复制代码
@PreDestroy
DisposableBean#destroy()
destroy-method

适合:

text 复制代码
关闭线程池
关闭连接
停止后台任务
释放本地资源

10.12 完整示例

java 复制代码
@Component
public class LifeCycleBean
        implements BeanNameAware,
                   InitializingBean,
                   DisposableBean {

    public LifeCycleBean() {
        System.out.println("1 constructor");
    }

    @Autowired
    public void setUserService(
            UserService userService) {

        System.out.println("2 dependency injection");
    }

    @Override
    public void setBeanName(
            String name) {

        System.out.println("3 BeanNameAware");
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("4 PostConstruct");
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println(
                "5 afterPropertiesSet");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("destroy prepare");
    }

    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

10.13 生产坑:@PostConstruct 做重 IO

错误:

java 复制代码
@PostConstruct
public void init() {
    remoteClient.queryAll();
}

问题:

text 复制代码
远程接口 30 秒
↓
Bean 初始化阻塞 30 秒
↓
ApplicationContext refresh 被拖慢
↓
服务启动变慢

更严重的是远程服务挂掉:

text 复制代码
整个应用可能无法启动

10.14 什么适合初始化阶段做

适合:

text 复制代码
校验必要配置
构建本地小型只读映射
初始化轻量 SDK 状态
注册本地资源

不适合:

text 复制代码
全表扫描
批量数据迁移
无限远程重试
大文件加载
大规模缓存预热

10.15 Prototype Bean 生命周期注意

Prototype Bean:

text 复制代码
Spring 负责创建和初始化

但通常不会像 singleton 一样持续管理到完整销毁。

因此:

text 复制代码
需要调用方自己管理某些销毁资源

这也是 prototype 不适合持有昂贵外部资源的原因之一。


[⬆ 返回总目录](#⬆ 返回总目录)

十一、Aware、初始化与销毁回调怎么选

11.1 业务 Bean 最推荐

普通初始化:

text 复制代码
@PostConstruct

普通销毁:

text 复制代码
@PreDestroy

优点:

text 复制代码
直观
少耦合 Spring API

11.2 第三方类

如果无法修改第三方源码:

java 复制代码
@Bean(
    initMethod = "start",
    destroyMethod = "close"
)
public ThirdPartyClient client() {
    return new ThirdPartyClient();
}

非常适合。


11.3 框架类

如果正在写框架:

text 复制代码
InitializingBean
BeanFactoryAware
ApplicationContextAware

可能更合适,因为框架本身本来就依赖 Spring。


11.4 避免 ApplicationContext.getBean 滥用

有些代码:

java 复制代码
@Component
public class SpringContextHolder
        implements ApplicationContextAware {

    static ApplicationContext context;
}

然后业务到处:

java 复制代码
SpringContextHolder.getBean(...)

这相当于:

text 复制代码
把依赖注入重新变成 Service Locator

会隐藏依赖,不利于测试。

应该只在:

text 复制代码
非常特殊的框架扩展
无法通过普通注入解决

时使用。


[⬆ 返回总目录](#⬆ 返回总目录)

十二、BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor、BeanPostProcessor

这三个名字很像,但处理阶段完全不同。


12.1 BeanFactoryPostProcessor

作用:

text 复制代码
Bean 实例化之前
修改 BeanDefinition

接口思想:

java 复制代码
void postProcessBeanFactory(
        ConfigurableListableBeanFactory factory);

12.2 使用场景

例如:

text 复制代码
批量修改 BeanDefinition 属性
动态改变 Scope
读取自定义元数据
框架配置处理

12.3 BeanDefinitionRegistryPostProcessor

它比普通 BeanFactoryPostProcessor 更早、更强。

因为可以直接拿:

text 复制代码
BeanDefinitionRegistry

动态:

text 复制代码
注册新的 BeanDefinition

12.4 经典实现

非常重要:

text 复制代码
ConfigurationClassPostProcessor

它解析:

text 复制代码
@Configuration
@ComponentScan
@Import
@Bean

所以 Spring Java Config 能工作,背后不是"注解自动变 Bean"的魔法。


12.5 BeanPostProcessor

作用对象不是:

text 复制代码
BeanDefinition

而是:

text 复制代码
已经实例化的 Bean 对象

12.6 对比

text 复制代码
BeanDefinitionRegistryPostProcessor
        ↓
增加/修改 BeanDefinition

BeanFactoryPostProcessor
        ↓
修改已有 BeanDefinition

BeanPostProcessor
        ↓
处理 Bean 实例

12.7 为什么 BeanPostProcessor 很强

因为它可以:

text 复制代码
读取 Bean
修改 Bean
包装 Bean
甚至返回另外一个对象

AOP 代理就是这种能力的重要应用。


12.8 常见重要 BeanPostProcessor

包括:

text 复制代码
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
AbstractAutoProxyCreator 体系

分别与:

text 复制代码
@Autowired
@PostConstruct/@PreDestroy
AOP 自动代理

等机制相关。


12.9 自定义 BeanPostProcessor 示例

java 复制代码
@Component
public class CostBeanPostProcessor
        implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(
            Object bean,
            String beanName) {

        System.out.println(
                "before init: " + beanName
        );

        return bean;
    }
}

12.10 为什么业务项目不要乱写全局 BPP

因为:

text 复制代码
它可能作用于大量 Bean

如果里面:

text 复制代码
做远程请求
持锁
修改对象
抛异常

会影响整个容器。


[⬆ 返回总目录](#⬆ 返回总目录)

十三、FactoryBean:为什么接口没有实现类也能注入代理对象

13.1 BeanFactory 与 FactoryBean 不要混

text 复制代码
BeanFactory
= Spring IoC 容器接口

FactoryBean
= 一种特殊 Bean

13.2 FactoryBean 的目的

某些对象创建复杂:

text 复制代码
动态代理
第三方 SDK
Mapper
RPC Client

无法简单:

java 复制代码
new Xxx()

FactoryBean 可以封装创建逻辑。


13.3 核心方法

概念上:

java 复制代码
public interface FactoryBean<T> {

    T getObject();

    Class<?> getObjectType();

    boolean isSingleton();
}

13.4 获取 FactoryBean 产物

假设 Bean 名:

text 复制代码
userMapper
java 复制代码
context.getBean("userMapper")

拿到通常是:

text 复制代码
FactoryBean.getObject()

返回的产物。


13.5 获取 FactoryBean 自己

java 复制代码
context.getBean("&userMapper")

前缀:

text 复制代码
&

表示:

text 复制代码
我要工厂 Bean 本身

13.6 MyBatis 为什么 Mapper 接口没实现类也能注入

核心思想:

text 复制代码
Mapper 接口
↓
MapperFactoryBean
↓
getObject()
↓
动态代理对象

所以:

java 复制代码
@Autowired
private UserMapper userMapper;

虽然没有:

java 复制代码
class UserMapperImpl

仍可以工作。


13.7 适合场景

FactoryBean 很适合:

text 复制代码
代理对象
复杂客户端
对象创建需要大量步骤
框架生成对象

13.8 不适合

普通业务 Bean:

java 复制代码
@Service
public class UserService

没有必要绕一层 FactoryBean。


[⬆ 返回总目录](#⬆ 返回总目录)

十四、Bean Scope 与单例 Bean 线程安全

14.1 Scope 是什么

Scope 决定:

text 复制代码
Bean 创建多少份
生命周期多长

14.2 singleton

默认 Scope:

text 复制代码
singleton

含义是:

text 复制代码
一个 Spring 容器内
一个 beanName 通常一个实例

注意:

text 复制代码
不是 JVM 绝对全局单例

两个 ApplicationContext:

text 复制代码
可以各自有一份

14.3 prototype

java 复制代码
@Scope("prototype")
@Component
public class TaskContext {
}

每次获取:

java 复制代码
context.getBean(TaskContext.class)

通常会创建新实例。


14.4 Web Scope

常见:

text 复制代码
request
session
application
websocket

它们依赖 Web ApplicationContext。


14.5 单例 Bean 线程安全吗

不保证。

Spring 保证的主要是:

text 复制代码
实例数量语义

不是:

text 复制代码
字段访问线程安全

14.6 错误例子

java 复制代码
@Service
public class UserService {

    private Long currentUserId;

    public void process(Long userId) {

        currentUserId = userId;

        // ...
    }
}

请求 A:

text 复制代码
userId = 1

请求 B 同时:

text 复制代码
userId = 2

字段会互相覆盖。


14.7 为什么大多数 Service 又没问题

因为通常是:

text 复制代码
无状态

例如:

java 复制代码
public UserVO getUser(Long id) {

    User user =
            repository.findById(id);

    return convert(user);
}

id、user:

text 复制代码
方法局部变量

每个线程都有自己的调用栈。


14.8 什么字段通常安全

text 复制代码
final 依赖
不可变配置
线程安全组件
只读常量

14.9 什么字段危险

text 复制代码
当前用户
临时 List
可变 Map
计数器
请求状态

14.10 Prototype 注入 singleton 的坑

如果 singleton Bean:

java 复制代码
@Service
public class OrderService {

    private final PrototypeBean prototype;

    public OrderService(
            PrototypeBean prototype) {
        this.prototype = prototype;
    }
}

构造 OrderService 时:

text 复制代码
只解析一次 PrototypeBean

后面字段仍然是同一个引用。

如果希望:

text 复制代码
每次调用都获取新的 prototype

需要:

text 复制代码
ObjectProvider
Lookup Method
其他 Provider 机制

[⬆ 返回总目录](#⬆ 返回总目录)

十五、循环依赖与三级缓存

这是 Spring 面试最经典、也最容易背错的主题。


15.1 什么叫循环依赖

java 复制代码
@Service
public class AService {

    @Autowired
    private BService bService;
}
java 复制代码
@Service
public class BService {

    @Autowired
    private AService aService;
}

依赖图:

text 复制代码
A → B
↑   ↓
└───┘

15.2 为什么会死循环

如果没有提前暴露机制:

text 复制代码
创建 A
↓
A 需要 B
↓
创建 B
↓
B 需要 A
↓
创建 A
↓
A 又需要 B
...

无法结束。


15.3 Spring 处理的是哪些循环依赖

经典三级缓存主要针对:

text 复制代码
单例 Bean
实例已经创建但初始化尚未完成
setter/字段等依赖注入场景

它不是:

text 复制代码
万能循环依赖解决器

15.4 三个核心缓存

核心类:

text 复制代码
DefaultSingletonBeanRegistry

概念上有:

text 复制代码
singletonObjects
earlySingletonObjects
singletonFactories

15.5 一级缓存 singletonObjects

存放:

text 复制代码
完整创建完成的单例 Bean

正常:

java 复制代码
getBean()

首先希望从这里直接拿。


15.6 二级缓存 earlySingletonObjects

存放:

text 复制代码
已经被其他 Bean 提前引用的早期 Bean

它还没有走完整生命周期。


15.7 三级缓存 singletonFactories

存放:

text 复制代码
ObjectFactory

不是直接 Bean。

这个工厂在真正需要早期引用时:

text 复制代码
可以决定返回什么

15.8 A → B → A 完整流程

第一步:实例化 A

text 复制代码
createBeanInstance(A)

A 已经有 Java 对象:

text 复制代码
但还没注入 B

第二步:A 加入三级缓存

Spring 放入:

text 复制代码
A → ObjectFactory

ObjectFactory 的职责:

text 复制代码
如果有人提前需要 A
生成 A 的 early reference

第三步:A 开始 populateBean

发现:

text 复制代码
需要 B

于是:

text 复制代码
getBean(B)

第四步:创建 B

B 实例化后开始注入属性。

发现:

text 复制代码
需要 A

于是:

text 复制代码
getBean(A)

第五步:查 A

text 复制代码
一级缓存:没有
二级缓存:没有
三级缓存:有 ObjectFactory

调用 Factory:

text 复制代码
得到 A 的 early reference

然后:

text 复制代码
放二级缓存
三级缓存对应工厂移除

第六步:B 注入 A

B 成功拿到:

text 复制代码
A 的早期引用

然后 B 完成初始化。

进入:

text 复制代码
一级缓存

第七步:A 注入 B

B 现在已经完整。

A 注入 B。

然后 A 继续初始化。

最终进入:

text 复制代码
一级缓存

15.9 为什么需要二级缓存

如果每次 B、C 都来依赖 A:

text 复制代码
不能每次都重新调用 Factory
重新生成不同 early proxy

第一次生成 early reference 后放二级缓存:

text 复制代码
后续都返回同一个 early reference

15.10 为什么需要三级缓存

经典面试追问:

二级缓存已经能放半成品 A,为什么还要三级?

关键不是:

text 复制代码
"为了多缓存一层"

而是:

text 复制代码
三级缓存保存的是"生成早期引用的工厂"

这样 Spring 可以延迟到:

text 复制代码
真的有人循环依赖 A

时,再决定:

text 复制代码
直接给原始 A
还是经过 getEarlyBeanReference()
得到提前代理对象

这与 AOP 代理对象一致性密切相关。


15.11 getEarlyBeanReference

AOP 自动代理创建器可以参与:

text 复制代码
early reference

如果 A 最终需要被代理:

text 复制代码
B 不能拿到原始 A
而容器最终却给其他人代理 A

否则同一个逻辑 Bean 会出现:

text 复制代码
两个不同引用语义

三级缓存提供了延迟代理机会。


15.12 为什么不是所有 AOP + 循环依赖都完美处理

真实场景比:

text 复制代码
A → B → A

复杂。

如果存在:

text 复制代码
多个 BeanPostProcessor
代理包装顺序
异步代理
特殊 TargetSource
动态增强

可能导致:

text 复制代码
raw bean 注入与最终代理不一致

Spring 也会检测部分这种问题。

所以不要把:

text 复制代码
"三级缓存能解决循环依赖"

理解成:

text 复制代码
所有复杂代理循环依赖都安全

15.13 构造器循环依赖为什么不行

java 复制代码
class A {
    A(B b) {}
}
java 复制代码
class B {
    B(A a) {}
}

创建 A 时:

text 复制代码
必须先拿到 B

A 自己甚至:

text 复制代码
还没有完成实例化

因此没有:

text 复制代码
A 半成品对象

可以放三级缓存。


15.14 prototype 循环依赖

Prototype 每次都要创建新对象,没有 singleton 缓存生命周期模型,所以经典三级缓存方案不能像 singleton 那样解决。


15.15 @Lazy 能不能"解决"

例如:

java 复制代码
public AService(
        @Lazy BService bService) {
}

Spring 可以注入:

text 复制代码
延迟代理

把真正解析推迟到使用时。

但这通常是:

text 复制代码
缓解依赖结构

不是证明设计合理。


15.16 最推荐的解决方式

优先:

text 复制代码
重新拆职责

例如:

text 复制代码
OrderService ↔ CouponService

可以抽:

text 复制代码
OrderCouponCoordinator

或:

text 复制代码
领域服务
事件

15.17 事件解耦例子

原来:

text 复制代码
OrderService
调用
PointService

PointService 又回调 OrderService。

可以:

text 复制代码
OrderService
↓
发布 OrderCreatedEvent
↓
PointListener

解除直接双向依赖。


15.18 面试复盘

Q:Spring 为什么有三级缓存?

推荐:

一级缓存保存完整单例,二级缓存保存已经产生过的早期引用,三级缓存保存能够生成早期引用的 ObjectFactory。三级缓存的关键价值不是简单解决普通循环依赖,而是延迟产生 early reference,使 AOP 自动代理创建器有机会在循环依赖场景中返回与最终 Bean 一致的代理引用。


[⬆ 返回总目录](#⬆ 返回总目录)

十六、AOP 核心概念

16.1 AOP 是什么

AOP:

text 复制代码
Aspect-Oriented Programming
面向切面编程

解决的是:

text 复制代码
横切关注点

例如:

text 复制代码
日志
事务
权限
监控
审计
缓存
重试
幂等

16.2 为什么 OOP 不够

假设 100 个 Service 方法都要:

text 复制代码
记录耗时

如果每个方法:

java 复制代码
long start = ...;
try {
    business();
} finally {
    logCost();
}

业务代码被基础设施污染。

AOP 可以把:

text 复制代码
"哪些方法要增强"

和:

text 复制代码
"增强做什么"

集中管理。


16.3 Aspect

切面:

text 复制代码
横切逻辑的模块化封装

例如:

java 复制代码
@Aspect
@Component
public class AuditAspect {
}

16.4 Join Point

连接点。

在 Spring AOP 中核心可以理解为:

text 复制代码
方法执行

Spring AOP 不是任意 Java 指令级织入。


16.5 Pointcut

切点:

text 复制代码
哪些方法需要增强

例如:

java 复制代码
execution(
    * com.demo.service..*(..)
)

16.6 Advice

增强逻辑:

text 复制代码
Before
After
AfterReturning
AfterThrowing
Around

16.7 Target

真实业务对象。

例如:

text 复制代码
OrderService 原始对象

16.8 Proxy

对外暴露的代理对象。

调用:

text 复制代码
proxy.createOrder()

代理先执行增强:

text 复制代码
事务
日志
权限

再调用 Target。


16.9 Advisor

Spring 底层更常用的组合概念:

text 复制代码
Advisor = Pointcut + Advice

表示:

text 复制代码
什么地方
执行什么增强

16.10 Around 示例

java 复制代码
@Aspect
@Component
public class CostAspect {

    @Around(
      "@annotation(CostTime)")
    public Object around(
            ProceedingJoinPoint pjp)
            throws Throwable {

        long start =
                System.nanoTime();

        try {
            return pjp.proceed();
        } finally {

            long cost =
                System.nanoTime() - start;

            System.out.println(
                pjp.getSignature()
                + " cost=" + cost);
        }
    }
}

16.11 适合 AOP

特别适合:

text 复制代码
横跨多个模块
逻辑相对统一
和业务主体无关

例如:

text 复制代码
日志
事务
权限
审计
耗时

16.12 不适合 AOP

不要把真正业务流程藏进切面:

text 复制代码
扣库存
创建订单
计算价格
发送核心指令

否则调用关系:

text 复制代码
肉眼看不到

可维护性极差。


[⬆ 返回总目录](#⬆ 返回总目录)

十七、Spring AOP 代理创建源码主线

17.1 谁负责自动代理

非常重要的体系:

text 复制代码
AbstractAutoProxyCreator

它本身属于:

text 复制代码
BeanPostProcessor

更具体地说会参与 Bean 实例化前后阶段。


17.2 为什么 AOP 能在 Bean 生命周期里生效

因为 Bean 创建:

text 复制代码
initializeBean()
↓
BeanPostProcessor after

时,自动代理创建器可以判断:

text 复制代码
这个 Bean 是否匹配某些 Advisor

如果匹配:

text 复制代码
创建 Proxy

最终返回:

text 复制代码
代理对象

17.3 主线

简化:

text 复制代码
Bean 创建
↓
postProcessAfterInitialization
↓
wrapIfNecessary
↓
寻找匹配 Advisor
↓
ProxyFactory
↓
创建 AopProxy
↓
JDK Proxy / CGLIB
↓
返回代理

17.4 wrapIfNecessary

核心思想:

text 复制代码
这个 Bean 是否应该被代理?

它会检查:

text 复制代码
基础设施类?
已经处理过?
有没有匹配 Advisor?

如果没有:

text 复制代码
返回原 Bean

如果有:

text 复制代码
createProxy()

17.5 ProxyFactory

ProxyFactory 汇总:

text 复制代码
Target
Advisor
接口
代理配置

再选择合适的 AopProxy。


17.6 为什么一个 Bean 有时不是原类对象

例如:

java 复制代码
OrderService bean =
    context.getBean(OrderService.class);

如果被 AOP:

text 复制代码
bean 可能是代理对象

所以:

text 复制代码
bean.getClass()

可能看到:

text 复制代码
JDK Proxy
或 CGLIB 生成类

17.7 生产排障

如果:

text 复制代码
AOP 注解没生效

先看:

text 复制代码
Bean 是否 Spring 管理
是否真的生成代理
方法是否匹配 Pointcut
调用是否经过代理
代理方式是否支持该方法

[⬆ 返回总目录](#⬆ 返回总目录)

十八、JDK 动态代理与 CGLIB

18.1 JDK 动态代理

基于:

java 复制代码
java.lang.reflect.Proxy
InvocationHandler

通常围绕:

text 复制代码
接口

创建代理。


18.2 示例

java 复制代码
UserService proxy =
    (UserService)
    Proxy.newProxyInstance(
        UserService.class.getClassLoader(),
        new Class[]{UserService.class},
        (p, method, args) -> {

            System.out.println("before");

            Object result =
                method.invoke(target, args);

            System.out.println("after");

            return result;
        }
    );

18.3 调用流程

text 复制代码
proxy.query()
↓
InvocationHandler.invoke()
↓
增强逻辑
↓
反射调用 target.query()

18.4 CGLIB

核心思想:

text 复制代码
生成目标类的子类

然后通过:

text 复制代码
重写可代理方法

插入拦截逻辑。


18.5 为什么 final 有限制

子类代理依赖:

text 复制代码
继承
方法覆盖

所以:

text 复制代码
final class
不能继承

final method
不能 override

自然难以通过子类代理增强。


18.6 private 方法为什么不适合代理增强

private 方法:

text 复制代码
不能被子类正常 override

而且代理模型本身主要围绕:

text 复制代码
外部方法调用

因此不要期待:

text 复制代码
给 private 方法加 @Transactional

能像 public Service 方法一样工作。


18.7 JDK 和 CGLIB 谁更快

不要背:

text 复制代码
"JDK 一定慢,CGLIB 一定快"

现代 JVM 优化下实际差异通常不是业务主要瓶颈。

选型首先看:

text 复制代码
代理模型
类型兼容性
框架默认行为

而不是微小调用开销。


[⬆ 返回总目录](#⬆ 返回总目录)

十九、Advice、Advisor、Pointcut 与拦截器链

19.1 一次代理调用不是只有一个切面

同一个方法可能同时有:

text 复制代码
事务
日志
权限
缓存
监控

所以实际是:

text 复制代码
Interceptor Chain

19.2 MethodInterceptor

底层增强常被统一适配成:

text 复制代码
MethodInterceptor

核心思想:

java 复制代码
Object invoke(
    MethodInvocation invocation)

内部:

java 复制代码
return invocation.proceed();

表示:

text 复制代码
继续执行下一个拦截器

19.3 调用链

text 复制代码
Proxy
↓
SecurityInterceptor
↓
TransactionInterceptor
↓
LogInterceptor
↓
Target Method

返回时反向退出。


19.4 为什么 @Around 很强

因为它控制:

text 复制代码
proceed() 前
proceed() 后
异常
返回值
是否继续执行

所以功能最强。


19.5 但 Around 也最危险

错误切面:

java 复制代码
@Around(...)
public Object around(...) {
    try {
        return pjp.proceed();
    } catch (Exception e) {
        return null;
    }
}

这会:

text 复制代码
吞掉异常

可能进一步导致:

text 复制代码
事务无法按预期回滚
调用方误认为成功

19.6 Advisor 顺序

多个增强顺序可通过:

text 复制代码
Ordered
@Order

等机制控制。

但不要过度依赖非常复杂的顺序耦合。

如果:

text 复制代码
切面 A 必须在 B 前
B 必须在 C 前
C 又依赖 A 状态

说明设计可能太隐式。


[⬆ 返回总目录](#⬆ 返回总目录)

二十、AOP 自调用失效与生产坑

20.1 经典问题

java 复制代码
@Service
public class OrderService {

    public void createOrder() {
        this.saveOrder();
    }

    @Transactional
    public void saveOrder() {
    }
}

很多情况下:

text 复制代码
saveOrder 事务不生效

20.2 根本原因

外部调用:

text 复制代码
Controller
↓
OrderService Proxy
↓
TransactionInterceptor
↓
Target

内部调用:

text 复制代码
Target.createOrder()
↓
this.saveOrder()

这里:

text 复制代码
this = Target 自己

没有重新经过 Proxy。

因此:

text 复制代码
TransactionInterceptor 没执行

20.3 不只是 @Transactional

同样影响典型:

text 复制代码
@Async
@Cacheable
自定义 AOP 注解

只要依赖代理拦截:

text 复制代码
绕过代理就可能失效

20.4 推荐解决

最清晰:

text 复制代码
拆成两个 Bean

例如:

java 复制代码
@Service
public class OrderService {

    private final OrderTxService txService;

    public void create() {
        txService.save();
    }
}

20.5 不推荐为了绕过问题滥用代理自取

例如:

text 复制代码
AopContext.currentProxy()

虽然某些场景可以使用,但业务代码依赖:

text 复制代码
当前一定存在代理上下文

耦合更强。


[⬆ 返回总目录](#⬆ 返回总目录)

二十一、Spring 声明式事务完整原理

21.1 什么叫事务

数据库事务核心目标:

text 复制代码
一组数据库操作
要么共同成功
要么按规则回滚

例如转账:

sql 复制代码
UPDATE account
SET balance = balance - 100
WHERE id = 1;

UPDATE account
SET balance = balance + 100
WHERE id = 2;

不能:

text 复制代码
只扣不加

21.2 没有 Spring 时

JDBC:

java 复制代码
Connection conn =
        dataSource.getConnection();

try {
    conn.setAutoCommit(false);

    updateA(conn);
    updateB(conn);

    conn.commit();

} catch (Exception e) {

    conn.rollback();
    throw e;

} finally {

    conn.close();
}

大量样板代码。


21.3 Spring 事务解决什么

Spring 抽象:

text 复制代码
事务管理器
事务定义
事务状态
资源同步

再通过 AOP:

text 复制代码
把事务边界自动套在业务方法外面

21.4 @Transactional

java 复制代码
@Transactional
public void transfer(
        long from,
        long to,
        BigDecimal amount) {

    debit(from, amount);
    credit(to, amount);
}

开发者只写:

text 复制代码
事务语义

不再手动 commit/rollback。


21.5 底层主线

text 复制代码
调用 Service Proxy
↓
TransactionInterceptor
↓
TransactionAspectSupport
↓
获取 TransactionManager
↓
getTransaction()
↓
开启/加入事务
↓
invocation.proceed()
↓
目标业务方法
↓
成功 → commit
异常 → rollback 判断

21.6 TransactionInterceptor

它本质是一个:

text 复制代码
MethodInterceptor

说明:

text 复制代码
声明式事务就是 AOP 拦截器

21.7 PlatformTransactionManager

经典抽象:

text 复制代码
PlatformTransactionManager

核心动作:

text 复制代码
getTransaction
commit
rollback

现代 Spring 事务 API 也在持续演进,但这个抽象仍是理解传统命令式事务的重要核心。


21.8 DataSourceTransactionManager

JDBC 场景典型管理器会:

text 复制代码
从 DataSource 获取 Connection
关闭 autoCommit
绑定当前线程
提交/回滚
释放资源

21.9 为什么一个事务里的 JDBC 操作能共享连接

关键:

text 复制代码
TransactionSynchronizationManager

它会把资源和当前线程关联。

可以简化理解:

text 复制代码
ThreadLocal
    ↓
DataSource → ConnectionHolder

所以同一线程中:

text 复制代码
JdbcTemplate
MyBatis-Spring

能获取到事务上下文中的连接。


21.10 注意:ThreadLocal 不等于"事务就是 ThreadLocal"

准确说:

text 复制代码
数据库事务最终还是数据库/JDBC Connection 实现

Spring 使用线程绑定机制:

text 复制代码
管理当前调用线程对应的事务资源

21.11 事务与数据库的关系

Spring:

text 复制代码
管理事务边界
统一编程模型
决定传播
决定回滚
绑定资源

数据库:

text 复制代码
真正完成 commit/rollback
MVCC
锁
undo
redo
隔离性

不要混淆。


21.12 事务适合场景

text 复制代码
订单主表 + 明细
转账
库存扣减
批量状态更新
业务状态与操作日志同库强一致

21.13 本地事务不解决什么

一个本地数据库事务不能自动保证:

text 复制代码
MySQL
+
Redis
+
Kafka
+
第三方 HTTP

四者原子提交。

这属于:

text 复制代码
分布式一致性

需要其他方案。

[⬆ 返回总目录](#⬆ 返回总目录)

二十二、TransactionManager、TransactionInterceptor 与线程资源绑定

22.1 TransactionManager 是什么

事务管理器负责把 Spring 的统一事务语义映射到具体资源。

例如:

text 复制代码
JDBC
JPA
JTA
R2DBC

不同技术底层事务 API 不同,Spring 用统一抽象屏蔽差异。


22.2 TransactionDefinition

描述:

text 复制代码
这个事务想要什么规则

包括:

text 复制代码
传播行为
隔离级别
超时
只读
名称

22.3 TransactionStatus

描述:

text 复制代码
当前事务运行状态

例如:

text 复制代码
是不是新事务
是否标记 rollback-only
是否完成

22.4 事务拦截器完整思路

text 复制代码
拿到 Method
↓
读取 @Transactional 元数据
↓
找到 TransactionManager
↓
根据 propagation 判断:
  新建?
  加入?
  挂起?
  不开?
↓
执行目标方法
↓
正常返回 → commit
异常 → rollback rule
↓
恢复被挂起事务
↓
清理线程资源

22.5 TransactionSynchronizationManager

常见管理内容:

text 复制代码
resources
synchronizations
transactionName
readOnly
isolationLevel
active flag

这些通常以线程上下文形式维护。


22.6 为什么多线程会打断事务上下文

主线程:

text 复制代码
Thread-1
DataSource → Connection-A

你突然:

java 复制代码
executor.submit(
    () -> repository.update(...)
);

子线程:

text 复制代码
Thread-2

没有 Thread-1 的绑定资源。

它可能:

text 复制代码
重新获取 Connection-B

自然不在原事务。


22.7 生产场景

假设:

text 复制代码
事务中批量查 10 万条数据
然后开 20 个线程更新

不要误以为:

text 复制代码
这 20 个线程天然属于同一个 @Transactional

如果需要并发数据库操作,必须重新设计事务边界和一致性方案。


[⬆ 返回总目录](#⬆ 返回总目录)

二十三、事务传播行为

Propagation 解决:

text 复制代码
事务方法 A 调用事务方法 B
B 到底用哪个事务?

23.1 REQUIRED

默认。

text 复制代码
当前有事务 → 加入
当前没事务 → 新建

业务:

text 复制代码
createOrder()
↓
saveOrder()
↓
saveDetail()

通常希望:

text 复制代码
一个事务

适合 REQUIRED。


23.2 REQUIRES_NEW

text 复制代码
总是需要独立新事务

如果外层有事务:

text 复制代码
外层挂起
↓
内层开新事务
↓
内层结束
↓
恢复外层

23.3 真实场景:操作日志

订单事务失败:

text 复制代码
业务回滚

但希望:

text 复制代码
失败审计日志仍然提交

可以考虑把日志服务做成单独 Bean:

java 复制代码
@Transactional(
    propagation =
        Propagation.REQUIRES_NEW
)
public void saveAuditLog(...) {
}

23.4 REQUIRES_NEW 的大坑

外层事务已经占:

text 复制代码
一个 Connection

内层新事务还要:

text 复制代码
另一个 Connection

高并发 + 小连接池时:

text 复制代码
连接池可能耗尽

所以不要到处使用。


23.5 SUPPORTS

text 复制代码
有事务 → 加入
没事务 → 非事务执行

适合:

text 复制代码
可在事务上下文中参与
但自身不强制需要事务

23.6 MANDATORY

text 复制代码
必须已有事务
否则报错

适合:

text 复制代码
明确要求只能作为某事务流程内部步骤

但使用频率不高。


23.7 NOT_SUPPORTED

text 复制代码
必须非事务执行
有外层事务则挂起

适合:

text 复制代码
明确不希望长期操作占用事务上下文

23.8 NEVER

text 复制代码
当前有事务就报错

用于强约束。


23.9 NESTED

嵌套事务。

在支持 Savepoint 的传统 JDBC 事务管理场景中,可以理解为:

text 复制代码
外层事务
↓
创建 Savepoint
↓
内层失败可以回滚到 Savepoint

23.10 NESTED 与 REQUIRES_NEW 的区别

REQUIRES_NEW

text 复制代码
两个独立数据库事务

内层提交后:

text 复制代码
外层以后回滚
内层通常仍已提交

NESTED

通常:

text 复制代码
仍在同一个物理事务里
使用 Savepoint

最终外层整体回滚:

text 复制代码
嵌套部分也无法独立存活

23.11 注意事务管理器能力

不是所有事务管理器都支持相同的 NESTED 语义。

不要只背注解枚举。

实际要看:

text 复制代码
底层资源
TransactionManager 实现

[⬆ 返回总目录](#⬆ 返回总目录)

二十四、事务隔离级别、readOnly、timeout、rollbackFor

24.1 Isolation 是什么

Spring 可以声明:

java 复制代码
@Transactional(
    isolation =
        Isolation.READ_COMMITTED
)

但是:

text 复制代码
真正隔离行为由数据库实现

24.2 常见隔离级别

text 复制代码
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE
DEFAULT

24.3 DEFAULT

表示:

text 复制代码
使用底层数据库/数据源默认隔离级别

24.4 不要把 Spring Isolation 和 MVCC 混在一起

Spring 只是:

text 复制代码
向底层 Connection/事务系统传递隔离要求

数据库的:

text 复制代码
MVCC
Read View
锁
undo

才决定具体读现象。


24.5 readOnly

java 复制代码
@Transactional(readOnly = true)
public UserVO query(...) {
}

它表达:

text 复制代码
这个事务主要用于读取

但不要误解成:

text 复制代码
"数据库绝对禁止任何 UPDATE"

具体优化/约束取决于:

text 复制代码
TransactionManager
数据库
ORM
驱动

它更多是:

text 复制代码
语义 + 优化提示

24.6 timeout

java 复制代码
@Transactional(timeout = 3)

表示:

text 复制代码
事务允许的超时时间语义

但具体超时传播到:

text 复制代码
数据库操作
查询
锁等待

的行为需结合具体事务管理器和数据访问框架。


24.7 rollbackFor

Spring 默认回滚规则主要针对:

text 复制代码
RuntimeException
Error

如果业务使用受检异常:

java 复制代码
@Transactional(
    rollbackFor = Exception.class
)

可以显式声明。


24.8 noRollbackFor

某类异常:

text 复制代码
业务上允许事务提交

可以:

java 复制代码
@Transactional(
    noRollbackFor =
        SomeBizException.class
)

但应谨慎。


24.9 rollback-only

如果内层代码把事务标记:

text 复制代码
rollback-only

即使外层最后没有异常,也可能在提交阶段发现:

text 复制代码
不能提交

出现:

text 复制代码
UnexpectedRollbackException

24.10 UnexpectedRollbackException 典型场景

text 复制代码
外层 REQUIRED
↓
内层 REQUIRED
↓
内层抛 RuntimeException
↓
事务被标记 rollback-only
↓
外层 catch 异常并继续
↓
外层以为能 commit
↓
实际提交发现 rollback-only
↓
UnexpectedRollbackException

这是非常经典的面试场景。


[⬆ 返回总目录](#⬆ 返回总目录)

二十五、@Transactional 失效场景

不要只背"八种失效"。

先记根本原则:

text 复制代码
声明式事务依赖:
Spring Bean
+
事务 Advisor
+
代理调用
+
正确事务资源
+
正确异常/传播规则

25.1 同类自调用

java 复制代码
this.txMethod();

绕过代理。


25.2 对象不是 Spring Bean

java 复制代码
OrderService service =
        new OrderService(...);

不会自动有 Spring 事务代理。


25.3 方法不可被当前代理模式有效拦截

实践中事务方法通常推荐:

text 复制代码
public

并避免:

text 复制代码
private/final 等容易与代理限制冲突的设计

具体规则需结合当前 Spring 版本和代理模式。


25.4 异常被 catch 吃掉

java 复制代码
@Transactional
public void create() {
    try {
        repository.insert();
        throw new RuntimeException();
    } catch (Exception e) {
        log.error("failed", e);
    }
}

外层拦截器看到:

text 复制代码
正常返回

可能提交。


25.5 回滚异常类型不匹配

受检异常:

java 复制代码
throw new IOException();

如果没有配置:

text 复制代码
rollbackFor

可能不符合默认回滚规则。


25.6 多线程

java 复制代码
@Transactional
public void process() {

    executor.submit(
        () -> repository.update()
    );
}

子线程不天然继承当前事务。


25.7 数据库/存储引擎不支持事务

Spring 只能:

text 复制代码
调用底层事务 API

底层没有能力,Spring 也无法凭空提供。


25.8 错误传播行为

例如:

text 复制代码
内层 REQUIRES_NEW

已经独立提交。

外层后来回滚:

text 复制代码
不能自动把内层已提交事务撤销

25.9 事务范围太大

这不叫"失效",但生产危害很大。

java 复制代码
@Transactional
public void process() {

    query();

    remoteCall(); // 8s

    sleep();

    update();
}

造成:

text 复制代码
连接长期占用
锁长期持有
吞吐下降
死锁概率上升

25.10 正确边界原则

事务里尽量:

text 复制代码
只做必须保持原子性的数据库操作

远程调用、慢 IO 应尽量:

text 复制代码
移出事务
或者重新设计一致性

[⬆ 返回总目录](#⬆ 返回总目录)

二十六、Spring MVC 整体架构与请求链路

Spring MVC 是 Spring Framework 的 Servlet Web MVC 框架。


26.1 核心思想

使用:

text 复制代码
Front Controller

也就是:

text 复制代码
DispatcherServlet

所有 MVC 请求统一先进入一个前端控制器。


26.2 为什么需要 Front Controller

如果每个 Servlet 自己处理:

text 复制代码
URL 映射
参数绑定
JSON
异常
视图
拦截器

框架能力会非常分散。

统一 DispatcherServlet 后:

text 复制代码
请求分发和扩展点集中管理

26.3 完整请求链

text 复制代码
HTTP Request
↓
Servlet Container
↓
Filter Chain
↓
DispatcherServlet
↓
HandlerMapping
↓
HandlerExecutionChain
↓
Interceptor preHandle
↓
HandlerAdapter
↓
ArgumentResolver
↓
Controller Method
↓
Service
↓
ReturnValueHandler
↓
HttpMessageConverter / View
↓
Interceptor
↓
HTTP Response

异常时:

text 复制代码
HandlerExceptionResolver

参与。


26.4 DispatcherServlet 不是业务 Controller

它本身是:

text 复制代码
调度中心

不处理业务。

职责:

text 复制代码
找 Handler
找 Adapter
调 Handler
处理 ModelAndView
异常解析
视图渲染

[⬆ 返回总目录](#⬆ 返回总目录)

二十七、DispatcherServlet、HandlerMapping、HandlerAdapter

27.1 HandlerMapping

负责:

text 复制代码
"这个请求应该找谁处理?"

例如:

java 复制代码
@GetMapping("/orders/{id}")
public OrderVO get(
        @PathVariable Long id) {
}

HandlerMapping 会根据:

text 复制代码
URL
HTTP Method
headers
params
consumes
produces

等条件匹配 HandlerMethod。


27.2 RequestMappingHandlerMapping

注解 Controller 的核心 Mapping 实现之一。

启动时它会扫描:

text 复制代码
@Controller
@RequestMapping
@GetMapping
@PostMapping

建立:

text 复制代码
RequestMappingInfo
→ HandlerMethod

映射表。


27.3 HandlerExecutionChain

除了 Handler,还带:

text 复制代码
Interceptor 列表

所以执行时能:

text 复制代码
preHandle
postHandle
afterCompletion

27.4 HandlerAdapter

为什么已经找到 Controller Method 还要 Adapter?

因为 DispatcherServlet 希望:

text 复制代码
不关心具体 Handler 怎么调用

于是使用适配器。

注解 Controller 典型由:

text 复制代码
RequestMappingHandlerAdapter

处理。


27.5 HandlerAdapter 内部做了很多事

包括:

text 复制代码
参数解析
数据绑定
校验
调用 Controller
处理返回值

所以"调用 Controller 方法"并不是简单:

java 复制代码
method.invoke(...)

27.6 生产排障

接口根本进不到 Controller:

先查:

text 复制代码
Filter
Mapping
Interceptor preHandle
Content-Type
请求方法
路径

不要只在 Controller 打断点。


[⬆ 返回总目录](#⬆ 返回总目录)

二十八、参数解析、数据绑定与 HttpMessageConverter

28.1 参数解析器是什么

假设:

java 复制代码
@GetMapping("/users/{id}")
public UserVO get(
        @PathVariable Long id,
        @RequestHeader("X-Tenant") String tenant) {
}

Spring 必须知道:

text 复制代码
Long id 从哪里来?
tenant 从哪里来?

这由:

text 复制代码
HandlerMethodArgumentResolver

体系完成。


28.2 常见参数来源

text 复制代码
@RequestParam
@PathVariable
@RequestHeader
@CookieValue
@RequestBody
ModelAttribute
ServletRequest
Principal

各自有不同 resolver。


28.3 @RequestParam

请求:

text 复制代码
GET /users?page=1&size=20

Controller:

java 复制代码
public List<UserVO> list(
    @RequestParam int page,
    @RequestParam int size) {
}

28.4 @PathVariable

text 复制代码
GET /users/100
java 复制代码
@GetMapping("/users/{id}")
public UserVO get(
    @PathVariable Long id) {
}

28.5 @RequestBody

请求 Body:

json 复制代码
{
  "name": "Tom",
  "age": 20
}

Controller:

java 复制代码
@PostMapping("/users")
public UserVO create(
    @RequestBody CreateUserRequest req) {
}

28.6 HttpMessageConverter

负责:

text 复制代码
HTTP Body
↔
Java Object

典型 JSON converter 会使用 JSON 库做:

text 复制代码
序列化
反序列化

28.7 为什么 Content-Type 错会 415

如果 Controller 要:

text 复制代码
application/json

客户端却发:

text 复制代码
text/plain

找不到合适的消息转换器/媒体类型匹配,就可能返回:

text 复制代码
415 Unsupported Media Type

28.8 为什么 Accept 可能导致 406

客户端:

text 复制代码
Accept: application/xml

服务端只有 JSON 输出能力。

可能:

text 复制代码
406 Not Acceptable

28.9 返回值处理

Controller:

java 复制代码
@RestController
public UserVO get() {
    return new UserVO(...);
}

不是 Controller 自己:

text 复制代码
手动 JSON.stringify

而是:

text 复制代码
HandlerMethodReturnValueHandler
↓
HttpMessageConverter
↓
Response Body

28.10 生产坑:返回 Entity

直接:

java 复制代码
return userEntity;

风险:

text 复制代码
暴露数据库字段
API 与表结构耦合
ORM 懒加载序列化问题
敏感字段泄露

建议:

text 复制代码
Request DTO
Command
Domain/Entity
Response VO

按职责分离。


[⬆ 返回总目录](#⬆ 返回总目录)

二十九、Validation 参数校验

29.1 Validation 是什么

Spring 集成 Jakarta Bean Validation。

典型:

java 复制代码
public class CreateUserRequest {

    @NotBlank
    private String name;

    @Min(1)
    @Max(150)
    private Integer age;

    @Email
    private String email;
}

Controller:

java 复制代码
public void create(
    @Valid
    @RequestBody CreateUserRequest req) {
}

29.2 为什么需要统一校验

避免:

java 复制代码
if (name == null) ...
if (age < 1) ...

散落整个系统。


29.3 适合做

text 复制代码
非空
长度
数字范围
正则
Email
格式约束

29.4 不适合替代业务校验

例如:

text 复制代码
库存是否足够
订单状态能否取消
用户是否属于该部门
优惠券是否过期且属于当前账户

这些需要:

text 复制代码
查数据库/领域状态

属于业务规则。


29.5 嵌套校验

DTO:

java 复制代码
class OrderRequest {

    @Valid
    @NotNull
    private Address address;
}

如果没有:

java 复制代码
@Valid

嵌套对象内部校验可能不会级联执行。


29.6 分组校验

可用于:

text 复制代码
新增
修改

不同约束。

但分组太多会:

text 复制代码
让 DTO 规则非常复杂

复杂业务校验更适合显式业务代码。


[⬆ 返回总目录](#⬆ 返回总目录)

三十、全局异常处理

30.1 为什么需要统一处理

不推荐每个 Controller:

java 复制代码
try {
} catch (...) {
}

会重复:

text 复制代码
错误码
日志
响应

30.2 @RestControllerAdvice

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(
        BizException.class)
    public ApiResult<?> handle(
            BizException e) {

        return ApiResult.fail(
                e.getCode(),
                e.getMessage()
        );
    }
}

30.3 HandlerExceptionResolver

Spring MVC 异常体系核心抽象。

DispatcherServlet 调用 Handler 发生异常后:

text 复制代码
遍历异常解析器

尝试将异常转换为:

text 复制代码
ModelAndView
Response
状态码

30.4 典型异常分层

可以区分:

text 复制代码
参数异常
业务异常
权限异常
系统异常

30.5 系统异常不要直接返回堆栈

客户端响应:

json 复制代码
{
  "code": "SYSTEM_ERROR",
  "message": "系统繁忙,请稍后重试",
  "traceId": "..."
}

服务端日志保留:

text 复制代码
完整 stack trace

30.6 HTTP 状态码

不要所有错误都:

text 复制代码
HTTP 200

除非公司协议明确要求。

REST 风格可合理使用:

text 复制代码
400
401
403
404
409
500

并配合业务错误码。


[⬆ 返回总目录](#⬆ 返回总目录)

三十一、Filter、Interceptor、AOP 如何选

31.1 Filter

属于:

text 复制代码
Servlet 规范

最靠近 Web 容器。

适合:

text 复制代码
CORS
traceId
请求包装
Header
安全过滤

31.2 Interceptor

属于:

text 复制代码
Spring MVC

知道:

text 复制代码
Handler
Controller

适合:

text 复制代码
登录状态
Controller 权限
MVC 层请求上下文

31.3 AOP

作用于:

text 复制代码
Spring Bean 方法

不限定 HTTP。

适合:

text 复制代码
Service 权限
数据权限
事务
审计
日志
监控

31.4 执行位置

大致:

text 复制代码
Filter
↓
DispatcherServlet
↓
Interceptor
↓
Controller
↓
Service Proxy / AOP

31.5 场景选择

所有 HTTP 请求生成 traceId

选:

text 复制代码
Filter

Controller 接口权限

选:

text 复制代码
Interceptor

Service 方法数据权限

选:

text 复制代码
AOP

[⬆ 返回总目录](#⬆ 返回总目录)

三十二、Spring Event 事件机制

32.1 是什么

Spring Event 是:

text 复制代码
进程内发布/订阅机制

核心:

text 复制代码
ApplicationEventPublisher
ApplicationListener
ApplicationEventMulticaster

32.2 例子

事件:

java 复制代码
public record OrderCreatedEvent(
        Long orderId) {
}

发布:

java 复制代码
publisher.publishEvent(
        new OrderCreatedEvent(orderId)
);

监听:

java 复制代码
@EventListener
public void onOrderCreated(
        OrderCreatedEvent event) {

    // ...
}

32.3 为什么要事件

原来:

text 复制代码
OrderService
├─ PointService
├─ SmsService
├─ AuditService
└─ StatisticsService

依赖越来越多。

事件:

text 复制代码
OrderService
↓
OrderCreatedEvent
├─ PointListener
├─ AuditListener
└─ StatisticsListener

减少直接耦合。


32.4 默认同步还是异步

普通 Spring Event 默认通常:

text 复制代码
同步调用监听器

所以:

text 复制代码
监听器执行 5 秒
发布事件的方法也会被拖慢

32.5 异步事件

可以结合:

text 复制代码
@Async

或配置异步事件广播策略。

但一旦异步:

text 复制代码
异常传播
事务上下文
执行顺序
可靠性

都要重新考虑。


32.6 TransactionalEventListener

用于:

text 复制代码
绑定事务阶段

例如:

java 复制代码
@TransactionalEventListener(
    phase =
        TransactionPhase.AFTER_COMMIT
)

表示:

text 复制代码
事务成功提交后再执行

32.7 真实场景

订单数据库事务:

text 复制代码
保存订单
↓
提交成功
↓
清理某本地缓存/执行弱一致后续逻辑

可以考虑 AFTER_COMMIT。


32.8 不要把 Spring Event 当 MQ

Spring Event 通常:

text 复制代码
进程内
无 Broker 持久化
服务重启不会重放

强可靠跨服务业务:

text 复制代码
MQ
Outbox
本地消息表

更合适。


[⬆ 返回总目录](#⬆ 返回总目录)

三十三、Resource 资源抽象

33.1 为什么需要 Resource

Java 原生资源来源很多:

text 复制代码
File
Classpath
URL
ServletContext
InputStream

Spring 统一抽象:

text 复制代码
Resource

33.2 常见实现

text 复制代码
ClassPathResource
FileSystemResource
UrlResource
ByteArrayResource
InputStreamResource

33.3 示例

java 复制代码
Resource resource =
        new ClassPathResource(
                "templates/a.txt"
        );

try (InputStream in =
        resource.getInputStream()) {
}

33.4 ResourceLoader

可以按统一路径:

text 复制代码
classpath:
file:
https:

加载资源。


33.5 真实场景

text 复制代码
读取模板
加载证书
加载规则文件
测试资源
框架配置文件

33.6 不适合做什么

不要用 Resource 去模拟:

text 复制代码
大型对象存储系统

几十 GB 文件、分片上传下载等应该用:

text 复制代码
OSS/S3 SDK

[⬆ 返回总目录](#⬆ 返回总目录)

三十四、ConversionService、Formatter、DataBinder

这一组经常被忽略,但理解 MVC 参数绑定很有帮助。


34.1 ConversionService

统一类型转换。

例如:

text 复制代码
String "100"
→ Long 100

核心 SPI:

text 复制代码
Converter<S,T>

34.2 自定义 Converter

java 复制代码
public class StringToOrderStatusConverter
        implements Converter<
            String,
            OrderStatus> {

    @Override
    public OrderStatus convert(
            String source) {

        return OrderStatus.valueOf(
                source.toUpperCase()
        );
    }
}

34.3 Formatter

更偏:

text 复制代码
文本格式化
本地化

典型:

text 复制代码
日期
金额

34.4 DataBinder

负责:

text 复制代码
把请求/属性值绑定到对象属性

同时可配合:

text 复制代码
ConversionService
Validator

34.5 WebDataBinder

Spring MVC 场景扩展。

可以:

text 复制代码
设置允许绑定字段
注册 Editor/Formatter

34.6 安全注意:Mass Assignment

如果直接把所有请求字段绑定到 Entity:

text 复制代码
客户端可能提交本不应该修改的字段

例如:

json 复制代码
{
  "name": "Tom",
  "role": "ADMIN"
}

如果 Entity 有 role 且自动绑定:

text 复制代码
可能形成越权

推荐使用:

text 复制代码
专用 Request DTO
明确允许字段

[⬆ 返回总目录](#⬆ 返回总目录)

三十五、SpEL 表达式语言

35.1 SpEL 是什么

SpEL:

text 复制代码
Spring Expression Language

可以动态访问:

text 复制代码
对象属性
方法
集合
条件
Bean

35.2 示例

java 复制代码
@Value("#{systemProperties['user.home']}")
private String home;

35.3 常见应用

Spring 生态中很多注解支持表达式:

text 复制代码
@Cacheable key
Spring Security 表达式
条件逻辑
部分配置

35.4 Cache Key 示例

java 复制代码
@Cacheable(
    cacheNames = "user",
    key = "#userId"
)
public User get(Long userId) {
}

#userId 就是表达式上下文的一部分。


35.5 不适合场景

不要把复杂业务规则全部塞进字符串表达式:

text 复制代码
可读性差
重构不安全
IDE 支持弱
运行期才暴露错误

35.6 安全风险

如果允许:

text 复制代码
不可信用户输入

被直接当成 SpEL 解析,可能产生严重安全风险。

不要设计:

text 复制代码
用户输入表达式 → 直接 parser.parseExpression()

除非做了严格沙箱和白名单。


[⬆ 返回总目录](#⬆ 返回总目录)

三十六、JdbcTemplate 与 Spring JDBC

36.1 为什么需要 JdbcTemplate

原生 JDBC:

java 复制代码
Connection
PreparedStatement
ResultSet
try/catch/finally
close

大量样板代码。

JdbcTemplate 把固定流程封装。


36.2 查询示例

java 复制代码
String sql = """
    SELECT id, name, age
    FROM user
    WHERE id = ?
    """;

User user =
    jdbcTemplate.queryForObject(
        sql,
        (rs, rowNum) ->
            new User(
                rs.getLong("id"),
                rs.getString("name"),
                rs.getInt("age")
            ),
        userId
    );

36.3 Update 示例

java 复制代码
jdbcTemplate.update(
    """
    UPDATE account
    SET balance = balance - ?
    WHERE id = ?
    """,
    amount,
    accountId
);

36.4 Template Method 思想

JdbcTemplate 帮你处理:

text 复制代码
获取 Connection
创建 Statement
执行
遍历
异常转换
资源释放

你只提供:

text 复制代码
SQL
参数
RowMapper

36.5 事务兼容

在 Spring 事务中:

text 复制代码
JdbcTemplate

会通过 Spring 资源管理机制拿到:

text 复制代码
当前事务绑定 Connection

而不是自己随便开一个无关连接。


36.6 适合

text 复制代码
SQL 可控
简单查询
批处理
不想引完整 ORM

36.7 不适合

复杂动态 SQL:

text 复制代码
大量条件拼接
对象映射复杂

可能 MyBatis 等更合适。


[⬆ 返回总目录](#⬆ 返回总目录)

三十七、数据访问异常统一转换

37.1 为什么需要

JDBC/数据库厂商异常:

text 复制代码
SQLState
SQLException
Vendor Code

业务很难统一处理。

Spring 提供:

text 复制代码
DataAccessException

异常体系。


37.2 好处

把不同技术异常统一成:

text 复制代码
DuplicateKeyException
DataIntegrityViolationException
DeadlockLoser 等语义化异常体系

具体类会随版本演进。


37.3 @Repository

@Repository 不只是语义标记。

Spring 可以通过异常转换后置处理器:

text 复制代码
把持久化技术异常
转换成 Spring DataAccessException

37.4 业务层不要到处 catch SQLException

更推荐:

text 复制代码
数据访问层异常抽象
↓
Service 根据业务需要转换
↓
BizException

[⬆ 返回总目录](#⬆ 返回总目录)

三十八、REST Client、WebClient 与 HTTP 调用

现代 Spring 同时提供多种 HTTP 客户端能力。


38.1 RestClient

适合:

text 复制代码
同步阻塞式 HTTP 调用

提供现代 fluent API。

示例概念:

java 复制代码
RestClient client =
    RestClient.create(
        "https://api.example.com");

UserVO user =
    client.get()
        .uri("/users/{id}", id)
        .retrieve()
        .body(UserVO.class);

38.2 WebClient

来自 WebFlux 模块。

适合:

text 复制代码
响应式/非阻塞 HTTP 客户端

38.3 什么时候用 RestClient

如果系统是:

text 复制代码
传统 Spring MVC
同步业务
每次 RPC 阻塞等待

RestClient 通常更直观。


38.4 什么时候用 WebClient

如果:

text 复制代码
本身使用 Reactive 链路
高并发 IO
需要流式数据

WebClient 更合适。


38.5 不要在 MVC 中盲目 WebClient.block()

如果:

text 复制代码
最终每次都 block()

很多响应式优势会消失。

不是说不能用,而是:

text 复制代码
架构收益要真实存在

38.6 生产 HTTP Client 必须考虑

text 复制代码
Connect Timeout
Read/Response Timeout
连接池
DNS
重试
幂等性
熔断
限流
大 Body
日志脱敏

38.7 为什么重试很危险

支付:

text 复制代码
POST /pay

超时了。

你不知道:

text 复制代码
服务端到底执行没执行

直接自动重试可能:

text 复制代码
重复支付

所以重试必须结合:

text 复制代码
幂等 Key
接口语义
错误类型

[⬆ 返回总目录](#⬆ 返回总目录)

三十九、异步任务、定时任务与线程池

39.1 @Async

Spring 提供:

java 复制代码
@Async

基于代理把调用提交到:

text 复制代码
TaskExecutor

39.2 底层

text 复制代码
Proxy
↓
Async Interceptor
↓
TaskExecutor
↓
Worker Thread
↓
Target

39.3 常见坑:自调用

java 复制代码
this.asyncMethod();

可能绕过代理。


39.4 异步异常

返回 void 的异步方法:

text 复制代码
异常不能像同步调用一样直接抛回调用线程

需要:

text 复制代码
AsyncUncaughtExceptionHandler
日志
监控

等处理。


39.5 强可靠任务不要只用 @Async

应用宕机:

text 复制代码
内存任务可能丢

强可靠通知:

text 复制代码
MQ

通常更适合。


39.6 @Scheduled

java 复制代码
@Scheduled(
    cron = "0 */5 * * * ?"
)
public void refresh() {
}

39.7 多实例重复执行

应用 5 个实例:

text 复制代码
通常 5 个都会跑定时任务

如果要求全局一次:

text 复制代码
分布式锁
调度中心
Leader

39.8 线程池生产原则

明确:

text 复制代码
corePoolSize
maxPoolSize
queueCapacity
keepAlive
rejection policy
thread name
metrics

不要:

text 复制代码
无界队列 + 无监控

[⬆ 返回总目录](#⬆ 返回总目录)

四十、Spring Cache

40.1 是什么

Spring Cache 是:

text 复制代码
缓存抽象

不是 Redis 本身。


40.2 @Cacheable

java 复制代码
@Cacheable(
    cacheNames = "product",
    key = "#id"
)
public ProductVO get(Long id) {
    return repository.query(id);
}

流程:

text 复制代码
Proxy
↓
CacheInterceptor
↓
计算 Key
↓
查 Cache
├─ 命中 → 返回
└─ 未命中 → 执行方法 → 写 Cache

40.3 @CachePut

text 复制代码
执行方法
并更新缓存

40.4 @CacheEvict

删除缓存。


40.5 适合

text 复制代码
字典
配置
商品详情
低更新频率查询
简单方法结果缓存

40.6 不适合只靠 Spring Cache 解决

text 复制代码
热点 Key
缓存击穿
大 Key
复杂批量缓存
逻辑过期
强一致缓存

这些要深入到具体缓存系统设计。


40.7 自调用同样有代理问题

text 复制代码
@Cacheable
@Transactional
@Async

都有共同根因:

text 复制代码
基于代理

这就是为什么学好 AOP 后很多知识会串起来。

[⬆ 返回总目录](#⬆ 返回总目录)

四十一、Spring Test 测试体系

Spring Framework 自带成熟测试支持。

不要把"测试"理解成:

text 复制代码
只要能跑 JUnit 就行

真正需要区分:

text 复制代码
纯单元测试
Spring Context 集成测试
MVC 测试
事务测试
Web 测试

41.1 纯单元测试

例如:

java 复制代码
class PriceServiceTest {

    @Test
    void should_calculate_discount() {

        PriceService service =
            new PriceService();

        BigDecimal result =
            service.calculate(
                new BigDecimal("100"));

        assertEquals(
            new BigDecimal("90"),
            result
        );
    }
}

不启动 Spring。

优点:

text 复制代码
快
稳定
定位问题直接

适合:

text 复制代码
算法
业务规则
领域服务
工具类

41.2 Spring TestContext Framework

当你需要:

text 复制代码
真实 Bean 注入
@Configuration
@Transactional
数据库
Spring 生命周期

可以启动 Spring Test Context。

它会帮助:

text 复制代码
创建 Context
缓存 Context
注入测试实例
管理事务

41.3 Context 缓存

测试框架会尽量复用:

text 复制代码
配置相同的 ApplicationContext

减少重复启动成本。

如果测试类不断使用不同:

text 复制代码
配置
Profile
Mock
Property

会导致:

text 复制代码
Context Cache Miss

测试变慢。


41.4 @DirtiesContext

表示:

text 复制代码
当前测试污染了 Context
后面别复用了

非常有用,但滥用会导致:

text 复制代码
大量 Context 重建
测试变慢

41.5 事务测试

测试方法可以:

java 复制代码
@Transactional
@Test
void testCreateOrder() {
}

很多 Spring Test 场景会:

text 复制代码
测试结束自动回滚

非常方便数据库测试。

但是要注意:

text 复制代码
测试里的事务边界
和真实 HTTP 请求事务边界
可能不一样

41.6 常见"测试通过,线上失败"

例如测试:

text 复制代码
整个测试方法一个事务

ORM 懒加载始终可用。

真实生产:

text 复制代码
Service 事务结束
Controller 序列化才访问懒加载属性

结果:

text 复制代码
线上 LazyInitializationException

所以测试要尽量模拟真实边界。


41.7 MockMvc

Spring MVC 测试可以:

text 复制代码
不真的打开网络端口

直接模拟 MVC 请求链。

示例:

java 复制代码
mockMvc.perform(
        get("/users/1"))
    .andExpect(
        status().isOk());

适合测试:

text 复制代码
URL Mapping
参数
校验
JSON
异常处理
Interceptor

41.8 WebTestClient

既可用于响应式 Web 测试,也能在某些 Spring Web 测试场景中提供 fluent API。


41.9 测试原则

推荐:

text 复制代码
大量纯单元测试
适量 Spring 集成测试
针对 Web 使用 MockMvc/WebTestClient
少量真正端到端测试

不要所有测试都启动完整系统。


[⬆ 返回总目录](#⬆ 返回总目录)

四十二、AOT 与现代 Spring

42.1 AOT 是什么

AOT:

text 复制代码
Ahead-of-Time
提前处理

Spring 传统运行时会:

text 复制代码
扫描
反射
解析 BeanDefinition
动态代理

AOT 希望把部分工作:

text 复制代码
提前到构建阶段

42.2 为什么需要

主要目标包括:

text 复制代码
更快启动
减少运行时动态发现
支持 GraalVM Native Image

42.3 AOT 会带来什么限制

当很多决策提前:

text 复制代码
Classpath 更固定
Bean 模型更固定
运行时动态改变能力减少

例如:

text 复制代码
动态注册未知 Bean
运行时任意反射

需要更明确的提示信息。


42.4 Runtime Hints

Native Image 构建需要知道:

text 复制代码
哪些类需要反射
哪些资源需要打包
哪些代理需要生成
哪些序列化类型需要保留

Spring 提供:

text 复制代码
RuntimeHints

体系支持。


42.5 什么时候值得关注

如果你开发:

text 复制代码
Spring Boot Native
Serverless
极快扩缩容
短生命周期程序

AOT 很重要。

普通长期运行企业后端:

text 复制代码
先把核心 Spring 原理学好

优先级更高。


[⬆ 返回总目录](#⬆ 返回总目录)

四十三、Spring 中常见设计模式

Spring 是学习设计模式非常好的源码项目。


43.1 工厂模式

典型:

text 复制代码
BeanFactory
FactoryBean

BeanFactory:

text 复制代码
管理对象创建

FactoryBean:

text 复制代码
封装复杂对象生产

43.2 单例模式思想

Spring singleton:

text 复制代码
容器级单例

由容器维护:

text 复制代码
singletonObjects

43.3 代理模式

最典型:

text 复制代码
AOP
@Transactional
@Async
@Cacheable

43.4 模板方法模式

例如:

text 复制代码
JdbcTemplate
AbstractApplicationContext

父类固定整体流程:

text 复制代码
公共步骤

子类只扩展:

text 复制代码
特定步骤

43.5 适配器模式

Spring MVC:

text 复制代码
HandlerAdapter

DispatcherServlet 不关心不同 Handler 的具体调用方式。


43.6 观察者模式

Spring Event:

text 复制代码
Publisher
Listener
Multicaster

43.7 责任链模式

AOP:

text 复制代码
Interceptor Chain

Servlet:

text 复制代码
Filter Chain

43.8 策略模式

Spring 内部大量:

text 复制代码
接口 + 多实现

例如:

text 复制代码
HandlerMapping
HandlerAdapter
TransactionManager
Resource

运行时选择合适实现。


43.9 面试不要只报模式名

好的回答:

Spring 的代理模式最典型用于 AOP 和声明式事务;模板方法可以看 JdbcTemplate 和 ApplicationContext refresh 体系;适配器模式体现在 Spring MVC 的 HandlerAdapter;观察者模式体现在 ApplicationEvent;责任链体现在 AOP MethodInterceptor 链和 Servlet Filter 链。


[⬆ 返回总目录](#⬆ 返回总目录)

四十四、生产环境性能问题与故障排查

真正工作中,Spring 知识最终要落到:

text 复制代码
问题怎么定位

44.1 Bean 注入失败

异常:

text 复制代码
NoSuchBeanDefinitionException

按顺序查:

text 复制代码
1. 类是不是 Bean?
2. 包有没有扫描?
3. @Bean 方法是否执行到?
4. @Profile 是否激活?
5. @Conditional 是否满足?
6. 类型是不是写错?
7. 是否模块依赖没引入?
8. BeanDefinition 是否动态注册失败?

44.2 多 Bean 冲突

异常:

text 复制代码
NoUniqueBeanDefinitionException

查:

text 复制代码
@Primary
@Qualifier
Bean Name
重复 @Bean
重复组件扫描
框架自动注册

44.3 BeanCurrentlyInCreationException

通常考虑:

text 复制代码
构造器循环依赖
复杂循环依赖
初始化阶段递归 getBean
BeanPostProcessor 提前创建

不要看到这个错误就机械说:

text 复制代码
"三级缓存失效了"

要看实际依赖图。


44.4 Bean 创建慢

可能:

text 复制代码
构造器慢
@PostConstruct 慢
BPP 慢
远程调用
数据库连接
大文件
锁

44.5 事务不生效

标准排查:

text 复制代码
是不是 Spring Bean?
↓
有没有事务代理?
↓
调用有没有经过代理?
↓
方法是否适合代理?
↓
异常是否被吞?
↓
rollback rule?
↓
传播行为?
↓
是不是跨线程?
↓
是不是同一个事务资源?

44.6 接口突然很慢

按链路:

text 复制代码
Filter
↓
Interceptor
↓
Controller
↓
AOP
↓
Service
↓
锁
↓
DB Pool
↓
SQL
↓
Redis
↓
RPC
↓
序列化

Spring 只是链路的一部分。


44.7 CPU 高

Spring 相关可能:

text 复制代码
AOP 高频复杂表达式
序列化大对象
大量 SpEL
死循环 Bean
错误重试
日志风暴

但要用:

text 复制代码
Profiler
JFR
Thread Dump

确认,不要猜。


44.8 内存上涨

检查:

text 复制代码
单例 Bean 持有大集合
无界本地缓存
ThreadLocal
线程池队列
ApplicationListener 持有对象
动态代理/类加载
缓存没有淘汰

44.9 ThreadLocal 泄漏

Web 容器线程:

text 复制代码
会被线程池复用

如果:

java 复制代码
threadLocal.set(user);

没有:

java 复制代码
remove();

下一个请求可能:

text 复制代码
读到旧数据

还可能造成内存引用无法及时释放。

推荐:

java 复制代码
try {
    threadLocal.set(ctx);
    chain.doFilter(...);
} finally {
    threadLocal.remove();
}

44.10 日志/审计 AOP 太重

如果一个切面:

text 复制代码
每个 Service 方法
同步写数据库
同步调远程审计

会给所有接口增加固定延迟。

横切逻辑的特点是:

text 复制代码
一旦慢
影响面非常大

所以切面要轻量、有监控。


44.11 代理层级过多

一个 Bean 可能被:

text 复制代码
事务
缓存
异步
权限
审计
监控

多个 Advisor 增强。

这不一定是问题,但如果调用链异常复杂:

text 复制代码
调试和异常顺序会越来越难

不要把所有业务需求都切面化。


44.12 Full GC 与 Spring

Spring 本身通常不是:

text 复制代码
Full GC 的直接原因

但容器单例持有对象生命周期长。

如果 singleton:

text 复制代码
Map 永不删除
List 无限增长
缓存无上限

这些对象自然长期存活。


[⬆ 返回总目录](#⬆ 返回总目录)

四十五、Spring 源码阅读路线

源码学习一定要:

text 复制代码
带问题看源码

不要一上来打开:

text 复制代码
spring-context

从第一行读到最后一行。


45.1 第一阶段:IoC 基本接口

先认识:

text 复制代码
BeanFactory
ListableBeanFactory
ConfigurableBeanFactory
ApplicationContext
BeanDefinition
BeanDefinitionRegistry

问题:

text 复制代码
Bean 怎么描述?
Bean 怎么存?
Bean 怎么取?

45.2 第二阶段:refresh()

入口:

text 复制代码
AbstractApplicationContext#refresh

目标:

text 复制代码
把容器启动十几个步骤讲顺

重点:

text 复制代码
invokeBeanFactoryPostProcessors
registerBeanPostProcessors
finishBeanFactoryInitialization

45.3 第三阶段:Bean 创建

核心:

text 复制代码
AbstractBeanFactory#doGetBean
AbstractAutowireCapableBeanFactory#createBean
AbstractAutowireCapableBeanFactory#doCreateBean
createBeanInstance
populateBean
initializeBean

目标:

text 复制代码
一个 Bean 从 BeanDefinition 到对象的完整流程

45.4 第四阶段:Autowired

看:

text 复制代码
AutowiredAnnotationBeanPostProcessor
DependencyDescriptor
DefaultListableBeanFactory#doResolveDependency
findAutowireCandidates

目标:

text 复制代码
为什么一个类型多个 Bean 会冲突
@Primary/@Qualifier 如何参与

45.5 第五阶段:循环依赖

核心类:

text 复制代码
DefaultSingletonBeanRegistry

核心结构:

text 复制代码
singletonObjects
earlySingletonObjects
singletonFactories

结合:

text 复制代码
doCreateBean
addSingletonFactory
getSingleton
getEarlyBeanReference

理解。


45.6 第六阶段:AOP

看:

text 复制代码
AbstractAutoProxyCreator
ProxyFactory
AopProxy
Advisor
MethodInterceptor
ReflectiveMethodInvocation

目标:

text 复制代码
代理何时创建
Advisor 怎么匹配
拦截器链怎么执行

45.7 第七阶段:事务

看:

text 复制代码
TransactionInterceptor
TransactionAspectSupport
PlatformTransactionManager
TransactionSynchronizationManager

目标:

text 复制代码
@Transactional 如何从注解变成 commit/rollback

45.8 第八阶段:Spring MVC

看:

text 复制代码
DispatcherServlet
RequestMappingHandlerMapping
RequestMappingHandlerAdapter
HandlerMethodArgumentResolver
HandlerMethodReturnValueHandler
HttpMessageConverter
HandlerExceptionResolver

目标:

text 复制代码
HTTP 请求怎样变成 Controller 方法调用

45.9 第九阶段:数据访问

看:

text 复制代码
JdbcTemplate
DataSourceUtils
SQLExceptionTranslator

理解:

text 复制代码
Connection 怎么参与事务
异常怎么转换

45.10 源码阅读技巧

每次只回答一个问题。

例如:

text 复制代码
@Autowired 字段到底在哪赋值?

就沿:

text 复制代码
populateBean
↓
AutowiredAnnotationBeanPostProcessor

看。

不要突然跳去:

text 复制代码
事务
MVC
AOT

[⬆ 返回总目录](#⬆ 返回总目录)

四十六、高频面试题与连续追问

下面按照真正面试"追问链"组织。


46.1 Spring 核心是什么?

第一层:

text 复制代码
IoC + AOP

追问:

IoC 到底反转什么?

text 复制代码
对象创建权
依赖管理权
生命周期管理权

追问:

DI 和 IoC 区别?

text 复制代码
IoC 是思想
DI 是主要实现方式

46.2 @Service 怎么变成 Bean?

text 复制代码
@ComponentScan
↓
Scanner
↓
读取 class 元数据
↓
生成 BeanDefinition
↓
注册 BeanDefinitionRegistry
↓
后续创建 Bean

追问:

扫描后马上 new 吗?

text 复制代码
不是
先注册 BeanDefinition

46.3 refresh 做什么?

不要背所有函数,先说:

text 复制代码
准备 BeanFactory
执行 BeanFactoryPostProcessor
注册 BeanPostProcessor
初始化事件等基础设施
创建非懒加载单例
完成刷新

46.4 Bean 生命周期?

text 复制代码
实例化
依赖注入
Aware
Before BPP
初始化回调
After BPP
代理
使用
销毁

追问:

AOP 大概什么时候产生?

通常和:

text 复制代码
BeanPostProcessor 初始化后处理

密切相关。


46.5 BeanFactoryPostProcessor 和 BeanPostProcessor?

text 复制代码
前者主要处理 BeanDefinition
后者处理 Bean 实例

46.6 FactoryBean 和 BeanFactory?

text 复制代码
BeanFactory = 容器
FactoryBean = 生产特殊对象的 Bean

46.7 Spring 如何处理循环依赖?

text 复制代码
单例 + 可提前暴露场景
使用经典三级缓存

追问:

三个缓存?

text 复制代码
singletonObjects
earlySingletonObjects
singletonFactories

追问:

为什么三级?

text 复制代码
延迟生成 early reference
兼容 AOP 代理

追问:

构造器为什么难解决?

text 复制代码
对象还没实例化
没有半成品可以暴露

46.8 AOP 底层?

text 复制代码
代理 + Advisor + MethodInterceptor Chain

追问:

Spring AOP Join Point 是什么?

核心是:

text 复制代码
方法执行

追问:

为什么 self-invocation 失效?

text 复制代码
this 调用没有重新经过 proxy

46.9 JDK 和 CGLIB?

text 复制代码
JDK 主要基于接口
CGLIB 基于子类

追问:

final 为什么有问题?

text 复制代码
子类不能继承/覆盖

46.10 @Transactional 原理?

text 复制代码
Transaction Advisor
↓
TransactionInterceptor
↓
TransactionManager
↓
目标方法
↓
commit/rollback

追问:

Connection 怎么共享?

text 复制代码
TransactionSynchronizationManager
线程绑定资源

追问:

新线程呢?

text 复制代码
不会天然继承

46.11 REQUIRED 和 REQUIRES_NEW?

REQUIRED:

text 复制代码
有加入、无新建

REQUIRES_NEW:

text 复制代码
独立新事务
外层挂起

追问:

REQUIRES_NEW 什么生产坑?

text 复制代码
额外占连接
高并发可能耗尽连接池

46.12 NESTED 和 REQUIRES_NEW?

text 复制代码
NESTED 常见基于 Savepoint
REQUIRES_NEW 是独立事务

46.13 为什么 catch 异常事务可能不回滚?

因为事务拦截器看到:

text 复制代码
目标方法正常返回

46.14 UnexpectedRollbackException 为什么出现?

text 复制代码
内层把共享事务标 rollback-only
外层吞异常后仍尝试提交
最终发现只能回滚

46.15 Spring MVC 流程?

text 复制代码
DispatcherServlet
↓
HandlerMapping
↓
HandlerAdapter
↓
ArgumentResolver
↓
Controller
↓
ReturnValueHandler
↓
HttpMessageConverter

46.16 Filter、Interceptor、AOP 区别?

text 复制代码
Filter:Servlet
Interceptor:Spring MVC
AOP:Spring Bean Method

46.17 @RequestBody 怎么变 Java 对象?

text 复制代码
ArgumentResolver
↓
HttpMessageConverter
↓
JSON 反序列化

46.18 Spring Event 是 MQ 吗?

不是。

text 复制代码
默认是进程内事件机制

46.19 Spring 单例 Bean 线程安全吗?

text 复制代码
不保证

无状态 Bean 通常可安全并发使用。


46.20 @Async、@Cacheable、@Transactional 为什么都有自调用问题?

因为共同核心:

text 复制代码
Spring AOP Proxy

这是一个很好的"知识串联题"。


[⬆ 返回总目录](#⬆ 返回总目录)

四十七、一张主线串起整个 Spring

最终你要形成下面这一条完整链路。

text 复制代码
                 Spring Framework
                        │
          ┌─────────────┴─────────────┐
          │                           │
        IoC                          AOP
          │                           │
          ↓                           ↓
   BeanDefinition                  Advisor
          │                   ┌──────┴──────┐
          ↓                   │             │
BeanDefinitionRegistry      Pointcut       Advice
          │                                  │
          ↓                                  ↓
   ApplicationContext                 MethodInterceptor
          │                                  │
          ↓                                  ↓
       refresh()                         Proxy
          │                                  │
   ┌──────┼─────────┐                        │
   │      │         │                        │
BFPP    BPP     Event/Resource               │
   │      │                                  │
   │      └──────────────────┐               │
   │                         │               │
   ↓                         ↓               │
BeanDefinition          Bean Creation        │
                         │                   │
                         ├─ instantiate      │
                         ├─ populateBean     │
                         ├─ @Autowired       │
                         ├─ Aware            │
                         ├─ init             │
                         └─ BPP after ───────┘
                                   │
                                   ↓
                               Proxy Bean
                                   │
                      ┌────────────┼─────────────┐
                      │            │             │
                Transaction      Cache         Async
                      │
                      ↓
             TransactionInterceptor
                      │
                      ↓
             TransactionManager
                      │
                      ↓
       TransactionSynchronizationManager
                      │
                      ↓
                 Connection
                      │
                      ↓
                   Database

循环依赖:
Bean A
  │
  ↓
实例化 A
  │
  ↓
三级缓存 ObjectFactory
  │
  ↓
创建 B
  │
  ↓
B 需要 A
  │
  ↓
getEarlyBeanReference(A)
  │
  ↓
二级缓存
  │
  ↓
B 完成
  │
  ↓
A 完成
  │
  ↓
一级缓存


Spring MVC:
HTTP
 ↓
Servlet Container
 ↓
Filter
 ↓
DispatcherServlet
 ↓
HandlerMapping
 ↓
Interceptor
 ↓
HandlerAdapter
 ↓
ArgumentResolver
 ↓
Controller
 ↓
Service Proxy
 ↓
Transaction / AOP
 ↓
Repository / JdbcTemplate
 ↓
DB / Redis / RPC
 ↓
ReturnValueHandler
 ↓
HttpMessageConverter
 ↓
JSON Response

最终学习标准

学完这份 Spring 专题后,你不应该只会说:

text 复制代码
Spring 有 IoC 和 AOP
Spring 用三级缓存
Spring 事务基于 AOP

而应该能完整解释:

text 复制代码
一个 @Service 是怎样从 class 文件变成 BeanDefinition
↓
BeanDefinition 怎样注册进 BeanFactory
↓
refresh() 做了什么
↓
Bean 怎样实例化
↓
@Autowired 怎样解析候选
↓
BeanPostProcessor 怎样参与生命周期
↓
为什么循环依赖要提前暴露
↓
为什么三级缓存保存 ObjectFactory
↓
AOP 代理什么时候生成
↓
代理调用怎样执行拦截器链
↓
@Transactional 怎样通过 TransactionInterceptor 工作
↓
Connection 为什么能在线程中共享
↓
为什么 self-invocation 和多线程会导致事务问题
↓
HTTP 请求怎样经过 DispatcherServlet 调到 Controller
↓
@RequestBody 怎样反序列化
↓
异常怎样被统一处理
↓
最终怎样返回 JSON

能把这条链脱稿讲清楚,Spring 就真正从"八股知识"变成了你的工程知识。


面试前 80 道自测题

  1. Spring Framework 是什么?
  2. 为什么需要 IoC?
  3. IoC 到底反转什么?
  4. IoC 和 DI 有什么区别?
  5. 为什么推荐构造器注入?
  6. 字段注入有什么问题?
  7. BeanDefinition 是什么?
  8. BeanDefinition 与 Bean 有什么区别?
  9. @Service 如何被发现?
  10. @ComponentScan 的底层思路是什么?
  11. @Bean 和 @Component 有什么区别?
  12. @Import 有哪些高级用法?
  13. ImportSelector 做什么?
  14. ImportBeanDefinitionRegistrar 做什么?
  15. BeanFactory 和 ApplicationContext 区别?
  16. refresh() 为什么是 Spring 核心?
  17. refresh 大致有哪些阶段?
  18. BeanFactoryPostProcessor 什么时候执行?
  19. BeanPostProcessor 什么时候注册?
  20. ConfigurationClassPostProcessor 作用是什么?
  21. 一个 Bean 从 getBean() 到创建完成经历什么?
  22. doCreateBean() 主要做什么?
  23. populateBean() 做什么?
  24. initializeBean() 做什么?
  25. @Autowired 谁处理?
  26. 多个同类型 Bean 如何选?
  27. @Primary 和 @Qualifier 区别?
  28. List/Map 注入有什么实际用途?
  29. ObjectProvider 适合什么场景?
  30. Bean 生命周期完整顺序是什么?
  31. Aware 有哪些典型接口?
  32. 为什么普通业务代码少用 ApplicationContextAware?
  33. @PostConstruct 适合做什么?
  34. @PostConstruct 做远程 IO 有什么风险?
  35. FactoryBean 与 BeanFactory 有什么区别?
  36. MyBatis Mapper 没实现类为什么能注入?
  37. singleton Bean 是否线程安全?
  38. 为什么无状态 Service 通常安全?
  39. prototype 注入 singleton 有什么坑?
  40. 什么是循环依赖?
  41. 三级缓存分别是什么?
  42. 为什么需要二级缓存?
  43. 为什么需要三级缓存?
  44. getEarlyBeanReference() 做什么?
  45. 构造器循环依赖为什么难解决?
  46. prototype 循环依赖为什么不同?
  47. 什么是 Aspect?
  48. 什么是 Pointcut?
  49. 什么是 Advice?
  50. Advisor 是什么?
  51. Spring AOP 的 Join Point 是什么?
  52. AOP 代理大概什么时候创建?
  53. AbstractAutoProxyCreator 做什么?
  54. JDK 动态代理原理是什么?
  55. CGLIB 原理是什么?
  56. final 为什么影响 CGLIB?
  57. MethodInterceptor Chain 怎么工作?
  58. 为什么 AOP self-invocation 会失效?
  59. @Transactional 底层原理是什么?
  60. TransactionInterceptor 做什么?
  61. TransactionManager 做什么?
  62. TransactionSynchronizationManager 做什么?
  63. 为什么同一个事务能共享 Connection?
  64. 为什么跨线程事务不自动传播?
  65. REQUIRED 与 REQUIRES_NEW 区别?
  66. NESTED 与 REQUIRES_NEW 区别?
  67. UnexpectedRollbackException 如何产生?
  68. 默认什么异常会回滚?
  69. readOnly 是不是绝对禁止 UPDATE?
  70. 事务里调用慢 RPC 有什么问题?
  71. Spring MVC 请求完整链路是什么?
  72. DispatcherServlet 做什么?
  73. HandlerMapping 做什么?
  74. HandlerAdapter 为什么存在?
  75. ArgumentResolver 做什么?
  76. HttpMessageConverter 做什么?
  77. Filter、Interceptor、AOP 怎么选?
  78. Spring Event 能否替代 MQ?
  79. JdbcTemplate 为什么能参与 Spring 事务?
  80. 如果线上 Spring 接口突然变慢,你怎么按层排查?

推荐复习顺序

如果目标是 Java 后端面试:

第一优先级

text 复制代码
IoC / DI
BeanDefinition
refresh()
Bean 生命周期
@Autowired
BeanPostProcessor
循环依赖
三级缓存

第二优先级

text 复制代码
AOP
JDK / CGLIB
Advisor / Interceptor Chain
@Transactional
事务传播
事务失效

第三优先级

text 复制代码
Spring MVC
参数解析
HttpMessageConverter
异常处理
Filter / Interceptor

第四优先级

text 复制代码
Event
Resource
ConversionService
SpEL
JdbcTemplate
REST Client
Async
Scheduled
Cache
Test
AOT

官方参考资料


建议的源码实战方式:

不要一次阅读整个 Spring 源码。可以自己写一个只有 5 个 Bean 的 Demo,然后分别在:

refresh() → doGetBean() → doCreateBean() → populateBean() → AutowiredAnnotationBeanPostProcessor → DefaultSingletonBeanRegistry#getSingleton() → AbstractAutoProxyCreator → TransactionInterceptor → DispatcherServlet

上打断点。每次只追踪一个问题。这样比看几十篇零散源码博客更容易形成自己的 Spring 主线。

相关推荐
东风破_2 小时前
从 Neo4j 到 GraphRAG:用 Text-to-Cypher 构建图检索 RAG
人工智能·后端
Wang's Blog2 小时前
Java框架 SpringCloud 快速入门: 实现 Feign 最佳实践(抽取方式)
java·spring cloud
MandalaO_O3 小时前
IDEA 开发(快捷键 + 调试 + 序列化)
java·ide·intellij-idea
xiaoqiMikko4 小时前
JVM 线上排查实战(七):jps 看不见它、jstack 连不上它,可它明明活得好好的
java·jvm
llqbzllll4 小时前
Spring AI 工具调用不是反射一下就结束:用 2.0.1 跑通失败恢复与调用上限
人工智能·后端
狼爷5 小时前
Rust/Go/Java/Python/PHP 大比拼:负载下后端框架到底差多少?
java·后端·编程语言
念何架构之路5 小时前
zap扩展生态与总结
java·前端·数据库
mantou1325 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
程序员老赵5 小时前
Docker 部署 DeepSeek Harness:轻松搭建局域网里的 AI Agent 平台
后端·ai编程·deepseek