# 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自动配置深度