# Spring 核心原理
用生活化的比喻,让你深入理解 IoC、Bean 生命周期和 AOP 的底层原理
前置知识:第01章 JVM 深度原理(类加载基础)+ 第03章 NIO 与 Netty(Pipeline 模式)
阅读指南(初学者必看)
为什么你需要学习 Spring 核心原理?
几乎所有的 Java 游戏后端都基于 Spring/SpringBoot。不理解底层原理,就无法:
- 理解 Bean 为什么要这么配置,出问题了怎么排查
- 理解 AOP 代理什么时候生效,什么时候不生效(同类调用不经过代理!)
- 开发自定义的 BeanPostProcessor 和 Starter
学完本章,你能回答:
- Spring 容器启动时做了什么?Bean 的完整生命周期是什么?
- @Autowired 注入的 Bean 是什么时候创建的?
- JDK 动态代理和 CGLIB 代理有什么区别?
- 为什么同类方法调用 AOP 不生效?
本文结构
第一部分:IoC 容器源码分析(容器启动流程)
第二部分:Bean 生命周期(从实例化到销毁)
第三部分:AOP 原理(动态代理两种方式)
一、IoC 容器源码分析
生活类比:IoC 容器就像一个人才中介公司——你不需要自己去找人,告诉中介你需要什么人,中介帮你找。
// Spring 容器启动流程(简化)
1. 读取配置(XML / 注解 / JavaConfig)
2. 解析为 BeanDefinition
3. 注册到 BeanDefinitionRegistry
4. 实例化所有 Bean(按依赖关系排序)
5. 依赖注入(@Autowired / 构造器注入)
6. 初始化回调(@PostConstruct / InitializingBean)
7. 就绪,可以使用了
BeanDefinition
生活类比:BeanDefinition 就像人才的简历——描述了要创建什么样的人。
BeanDefinition 包含:
├── beanClass ── 类名(谁)
├── scope ── 作用域(singleton/prototype)
├── lazyInit ── 是否懒加载
├── dependsOn ── 依赖的其他 Bean
├── initMethod ── 初始化方法
├── destroyMethod ── 销毁方法
└── propertyValues ── 属性值(构造器参数、setter参数)
容器启动详细流程
1. 创建 ApplicationContext
│
2. refresh() ── 核心方法
│
├── prepareRefresh() ── 准备工作
├── obtainFreshBeanFactory() ── 创建 BeanFactory
├── prepareBeanFactory() ── 配置 BeanFactory
├── postProcessBeanFactory() ── BeanFactory 后处理
├── invokeBeanFactoryPostProcessors() ── 执行 BeanFactoryPostProcessor
│ └── 这里扫描 @Component、@Configuration,注册 BeanDefinition
├── registerBeanPostProcessors() ── 注册 BeanPostProcessor
├── initMessageSource() ── 国际化
├── initApplicationEventMulticaster() ── 事件广播
├── onRefresh() ── 子类扩展
├── registerListeners() ── 注册监听器
├── finishBeanFactoryInitialization() ── 实例化所有非懒加载的 Bean ⭐
│ └── getBean() → createBean() → doCreateBean()
└── finishRefresh() ── 完成刷新,发布事件
二、Bean 生命周期
实例化 → 属性赋值 → 初始化 → 使用 → 销毁
│ │ │ │
├─ 构造器 ├─ @Autowired ├─ @PostConstruct ├─ @PreDestroy
└─ Factory └─ setter ├─ InitializingBean └─ DisposableBean
└─ init-method
完整生命周期(含扩展点)
1. 实例化(Instantiation)
├── 构造器
└── FactoryMethod
2. 属性赋值(Populate)
├── @Autowired 注入
└── setter 注入
3. Aware 回调
├── BeanNameAware.setBeanName()
├── BeanFactoryAware.setBeanFactory()
└── ApplicationContextAware.setApplicationContext()
4. BeanPostProcessor.postProcessBeforeInitialization()
│ ← @PostConstruct 在这里执行
5. 初始化(Initialization)
├── @PostConstruct
├── InitializingBean.afterPropertiesSet()
└── init-method
6. BeanPostProcessor.postProcessAfterInitialization()
│ ← AOP 代理在这里创建
7. 使用(Ready)
8. 销毁(Destruction)
├── @PreDestroy
├── DisposableBean.destroy()
└── destroy-method
游戏服务器中的 Bean 生命周期应用
@Component
public class RoomManager implements InitializingBean, DisposableBean {
@Autowired
private PlayerService playerService; // 属性注入
@PostConstruct
public void init() {
// 初始化房间池
System.out.println("RoomManager 初始化完成");
}
@Override
public void afterPropertiesSet() {
// 所有属性注入完成后执行
System.out.println("所有依赖已注入");
}
@PreDestroy
public void cleanup() {
// 等待所有房间结束
System.out.println("RoomManager 正在清理");
}
@Override
public void destroy() {
// 释放资源
System.out.println("RoomManager 已销毁");
}
}
三、AOP 原理
// 动态代理两种方式
// 1. JDK Proxy:基于接口
// 2. CGLIB:基于子类继承
// JDK Proxy 示例
@Service
public class GameService {
@LogExecutionTime // 自定义注解
public void processBattle() { ... }
}
// Spring 会为 GameService 创建代理对象
// 调用 processBattle() 时,先执行 @LogExecutionTime 的切面逻辑
JDK 动态代理 vs CGLIB
| JDK Proxy | CGLIB | |
|---|---|---|
| 原理 | 基于接口,生成接口的实现类 | 基于继承,生成目标类的子类 |
| 要求 | 目标类必须实现接口 | 目标类不能是 final |
| 性能 | 创建快,调用稍慢 | 创建慢,调用更快 |
| Spring 默认 | 有接口时用 JDK Proxy | 无接口时用 CGLIB |
| SpringBoot 默认 | — | 全部用 CGLIB(spring.aop.proxy-target-class=true) |
AOP 核心概念
生活类比:AOP 就像医院的分诊台——不管你看什么科,都要先经过分诊(量体温、挂号),这就是"切面"。
切面(Aspect)── 分诊台
通知(Advice)── 分诊台做的事(量体温、挂号)
├── @Before ── 看病前量体温
├── @After ── 看病后拿药
├── @Around ── 看病前后都管(最强大)
├── @AfterReturning ── 正常看完病后
└── @AfterThrowing ── 看病出问题时
切点(Pointcut)── 哪些人需要分诊
连接点(JoinPoint)── 具体哪个病人
⚠️ 同类调用 AOP 不生效
@Service
public class GameService {
public void startGame() {
// 这里调用 processBattle(),AOP 不生效!
// 因为 this.processBattle() 调用的是原始对象,不是代理对象
processBattle(); // ❌ @LogExecutionTime 不生效
}
@LogExecutionTime
public void processBattle() {
// 战斗逻辑
}
}
// 解决方案:
// 1. 注入自身(推荐)
@Autowired
private GameService self; // 注入的是代理对象
public void startGame() {
self.processBattle(); // ✅ AOP 生效
}
// 2. 从 ApplicationContext 获取代理对象
// 3. 使用 AopContext.currentProxy()
自问自答
Q:Spring 为什么默认单例?游戏服务器应该用单例吗? A:单例减少创建/销毁开销,大多数 Bean 是无状态的(Service、DAO),单例安全。有状态的 Bean(如玩家 Session)应该用 prototype 作用域或自定义管理。
Q:@Autowired 和构造器注入哪个好? A:构造器注入更好。1)可以声明不可变字段(final);2)依赖关系显式化;3)避免 NPE(Spring 保证构造器注入的依赖不为 null);4)方便单元测试。
Q:Bean 的创建顺序怎么确定? A:Spring 通过依赖关系自动排序:B 依赖 A,则 A 先创建。也可以用 @DependsOn 显式指定,或实现 Ordered/PriorityOrdered 接口。
Q:BeanPostProcessor 和 BeanFactoryPostProcessor 有什么区别? A:BeanFactoryPostProcessor 在 Bean 实例化之前执行,可以修改 BeanDefinition(如修改属性值)。BeanPostProcessor 在 Bean 实例化之后执行,可以修改 Bean 实例(如创建代理对象)。
实践任务
- 任务1:写一个自定义 BeanPostProcessor,在 Bean 初始化前后打印日志
- 任务2:用 @Order 控制多个 BeanPostProcessor 的执行顺序
- 任务3:实现 @LogExecutionTime 注解 + AOP 切面,统计方法耗时
- 任务4:验证同类调用 AOP 不生效,并用注入自身的方式修复
- 任务5:用 ApplicationContextAware 获取 Spring 容器,动态获取 Bean
与其他章节的关联
| 本章内容 | 关联章节 | 关联点 |
|---|---|---|
| IoC 容器 | 第05章 SpringBoot 自动配置 | 自动配置本质是注册 BeanDefinition |
| Bean 生命周期 | 第07章 字节码与动态编程 | AOP 代理用字节码技术生成 |
| AOP 代理 | 第03章 NIO 与 Netty | Pipeline 和 Filter Chain 模式类似 |
| BeanPostProcessor | 第05章 SpringBoot 自动配置 | 自动配置大量使用 BeanPostProcessor |
| 依赖注入 | 第11章 游戏服务器架构 | 游戏服务组件用 Spring 管理 |
上一章:03-Java-NIO与Netty | 下一章:05-SpringBoot自动配置深度