一、IOC(控制反转)
IOC 的核心作用是对象的创建和管理权不再由我们自己负责,而是交给 Spring 容器。这不仅包括了 Bean 的实例化,还包括它们之间的依赖关系注入,生命周期的管理等等
1,Spring 是怎么 "找到" 这些 Bean 的?
主要依赖两个关键注解:
- @ComponentScan:告诉 Spring 应该去扫描哪些包下的类
- @Component / @Controller / @Service / @Repository:告诉 Spring 哪些类是 Bean,需要交给 Spring 管理
Spring 启动后会进行扫描,将这些类注册为 Spring 容器中的 Bean,默认作用域为单例(@Scope("singleton"))。
2,除了注解,还能用 Java 配置类
- 使用 @Configuration 声明一个配置类
- 使用 @Bean 手动注册第三方组件或自定义 Bean
二、AOP:面向切面编程
AOP 是 Spring 中另一个非常强大特性,它可以在不侵入业务代码的前提下,动态插入逻辑,完成诸如日志记录,权限校验,异常处理,性能监控等横切关注点的功能。
核心流程:
- 在方法上打上注解,比如 @RateLimit(limit=5)
- Spring 会扫描这些注解,并找到定义了相关切面的 @Aspect 类
- 在这些方法执行前,后或发生异常时,插入自定义逻辑
实现方式:
- Spring 启动时扫描 @Aspect 类和对应的切点表达式
- 根据注解和表达式生成代理对象
- 在调用时执行代理逻辑,实现自动增强
三、Bean 生命周期和线程安全
Spring 容器不仅负责创建和注入 Bean,还负责管理 Bean 的整个生命周期。
生命周期包括:
- 实例化:对象被创建(相当于在堆内存 new 了一个对象)
- 属性注入:属性或依赖的 Bean 被注入(此时对象已经创建完成)
- 感知接口调用:如 ApplicationContextAware
- 初始化:这个初始化是从 Spring 的角度来看:
- @PostConstruct
- 实现 InitializingBean 接口
- 自定义 init-method
- BeanPostProcessor 的前置后置处理
- 销毁:
- @PreDestroy
- 实现DisposableBean
- 自定义 destroy-method
每一步都可以被扩展或钩住,Spring 提供了大量钩子接口用于增强 Bean 的创建和销毁过程。
默认 Bean 是单例的,线程安全吗?
- Spring 中的 Bean 默认是单例 @Scope("singleton"),这意味着容器中只维护一份实例
- 如果这个 Bean 中有成员变量,在多线程环境中会发生共享,不具备线程安全性
- 常见解决方案:
- 避免成员变量(无状态设计)
- 设计作用域为 @Scope("prototype"),每次都是一个新的对象
- 使用线程安全的同步机制:锁,原子类,ThreadLocal等
这在 Web 项目更为重要,比如 Controller 本身是单例的,就不能在里面定义共享变量用于存储业务数据。
四、Bean 创建过程中的循环依赖和缓存机制
Spring 容器在创建 Bean 时,为了解决循环依赖问题,引入了三级缓存机制。
三级缓存
- 一级缓存,singletonObjects,存放已经创建完整的 Bean (属性已经注入,初始化完成),也是最终存放 Bean 的位置
- 二级缓存,earlySingletonObjects,存放半成品 Bean(属性未注入完,但可能被其他 Bean 引用),仅用于解决循环依赖
- 三级缓存,singletonFactories,存放半成品代理对象的工厂函数(能生成代理),避免暴露半成品 Bean,支持 AOP
没有循环依赖时的创建流程
- getBean("A") -> 实例化 A,将工厂函数放到三级缓存
- 属性注入
- 初始化阶段(调用@PostConstruct / init-method)
- 判断是否需要增强,如果有 AOP,生成代理对象
- 成品 A 对象(或者代理 A 对象)放入一级缓存,清理临时缓存
虽然 Spring 也会把 ObjectFactory 放到三级缓存中,但由于创建过程中没有其他对象引用A,所以这个工厂函数不会被调用,最终只会放入一级缓存,二级 、三级缓存不会真正生效。
循环依赖中的流程
以 A 和 B 的相互依赖为例:
- getBean("A") -> 实例化 A -> 将生产代理的工厂函数放到三级缓存
- 注入 A 的属性 -> 发现需要 B -> getBean("B")
- 创建 B -> 实例化对象,放入三级缓存
- 注入 B 的属性 -> 发现需要 A -> 从三级缓存中获取 A 的工厂函数,生成 A 的代理对象 -> 放入二级缓存
- 注入 B.a = 代理A 对象,B 对象创建完成 -> 放入一级缓存
- 回到 A,完成属性注入 -> 将最终的 A 对象放入一级缓存
为什么需要三级缓存?
如果只有二级缓存,在刚才的第4步中,只能把没有增强的 A 原始对象给到 B,才能完成 B 对象的创建
在 AOP 场景下(如 A 使用 @Transactional),需要的是代理增强对象,将 A 原始对象暴露出去,会导致事务不生效。
三级缓存的引入,本质是为了解决 循环依赖 + AOP 的冲突问题
其他问题
构造器注入无法解决循环依赖,因为构造阶段还没有机会提前暴露对象
五、小结
- IOC:让 Spring 管理对象,依赖注入
- AOP:让 Spring 实现自动植入逻辑,提供可维护性
- 生命周期管理:让 Spring 接管对象的创建,初始化和销毁,并提供钩子增强能力
- 三级缓存:在循环依赖中,Spring 通过三级缓存,保证提前暴露出去的是正确的代理 Bean