Spring 核心知识点(IOC + AOP + 事务)
一、IOC 容器
1. Bean 完整生命周期
Spring 中 Bean 从创建到销毁遵循固定执行流程,AOP 代理对象也在此流程中生成。
实例化 Bean(分配内存空间)
↓
属性赋值(依赖注入,如 @Autowired)
↓
检查 Aware 接口(注入 BeanName、BeanFactory 等容器资源)
↓
BeanPostProcessor 前置处理(postProcessBeforeInitialization)
↓
初始化阶段(执行 InitializingBean 接口逻辑、自定义 init-method)
↓
BeanPostProcessor 后置处理(postProcessAfterInitialization,生成 AOP 代理对象)
↓
Bean 就绪(对外提供服务)
↓
销毁阶段(执行 DisposableBean 接口逻辑、自定义 destroy-method)
2. 三种依赖注入方式
2.1 构造器注入
通过类构造函数完成依赖注入。Spring 4.3 及以上版本中,若类仅有一个构造方法,可省略 @Autowired 注解。
java
@Component
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
2.2 Setter 方法注入
通过标准 setter 方法注入依赖,需配合 @Autowired 使用。
java
@Component
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
2.3 字段注入
直接在成员属性上添加注解实现注入,写法简洁。
java
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
}
3. 循环依赖
3.1 核心原理
Spring 依靠三级缓存 解决单例 Bean 的属性/Setter 注入循环依赖,核心思路可总结为:先实例化半成品对象存入缓存,后续再完成属性赋值。
- 属性/Setter 注入:Spring 先通过构造器实例化 Bean(半成品对象,已分配内存地址),将其存入缓存,再执行属性赋值。循环依赖发生时,另一方可从缓存中获取半成品引用,正常完成注入。
- 构造器注入 :实例化时必须一次性传入所有构造参数,无法产出半成品对象,不能存入缓存,因此无法解决循环依赖 ,直接抛出
BeanCurrentlyInCreationException异常。
3.2 普通 Bean 循环依赖执行流程
- 实例化
ServiceA,并将其对应的工厂对象放入三级缓存; - 为
ServiceA做属性赋值,发现依赖ServiceB,暂停当前流程,开始创建ServiceB; - 实例化
ServiceB,同样将工厂对象放入三级缓存; ServiceB无额外依赖,依次完成属性赋值、初始化、AOP 代理生成,成为完整 Bean 并存入一级缓存,同时清理自身在二、三级缓存中的数据;- 回到
ServiceA流程,从一级缓存获取完整的ServiceB完成注入; ServiceA执行后续初始化、代理生成逻辑,存入一级缓存,清理自身二、三级缓存数据。
3.3 存在 AOP 代理的循环依赖
三级缓存中存放的并非 Bean 实例,而是对象工厂(Lambda/匿名内部类),用于按需提前生成代理对象,打破 Spring「初始化完成后再创建代理」的默认规则。
java
// 三级缓存工厂核心逻辑
getEarlyBeanReference(beanName, mbd, bean) {
// 判断当前 Bean 是否需要生成 AOP 代理(如加了 @Transactional)
if (需要代理) {
// 提前创建并返回代理对象
return createProxy(bean);
} else {
// 无需代理,直接返回原始对象
return bean;
}
}
执行流程:
- 实例化 Bean A,将对象工厂存入三级缓存;
- A 进行属性赋值时发现依赖 B,转而创建 B;
- B 实例化后发现依赖 A,一、二级缓存均未命中,触发三级缓存中的 A 对象工厂;
- 工厂提前为 A 生成 AOP 代理对象,并将代理对象存入二级缓存;
- B 获取 A 的代理对象完成注入,自身正常走完生命周期,存入一级缓存;
- 回到 A 的流程,A 完成自身生命周期,Spring 检测到代理已提前生成,直接从二级缓存取出代理对象移入一级缓存。
补充说明:若无 AOP 代理,仅二级缓存即可解决循环依赖;引入 AOP 后,必须依靠三级缓存的对象工厂,按需提前生成代理。
4. IOC 容器完整创建流程
整体分为三大阶段:配置解析、Bean 定义注册、Bean 实例化。
阶段一:准备与解析配置
-
prepareRefresh 容器预处理
记录容器启动时间、更新运行状态,加载系统环境变量与配置文件(如
application.properties)。 -
obtainFreshBeanFactory 创建并初始化 Bean 工厂
创建底层容器
DefaultListableBeanFactory,通过配置读取器(注解/XML 读取器)扫描项目资源。
阶段二:注册 BeanDefinition
-
注册 BeanDefinition
BeanDefinition可理解为 Bean 的设计图纸 ,仅封装类名、作用域、懒加载、依赖等元数据,不会创建实例。所有BeanDefinition以beanName为键,存入集合beanDefinitionMap。 -
执行 BeanFactory 后置处理器
Spring 核心扩展点,可在 Bean 实例化前修改
BeanDefinition。典型应用:PropertyPlaceholderConfigurer解析配置文件占位符。 -
注册 BeanPostProcessor
仅完成接口实现类的实例化与统一存放,暂不执行逻辑,用于后续 Bean 生命周期中做拦截处理(如 AOP)。
阶段三:批量实例化单例 Bean
- finishBeanFactoryInitialization 预实例化非懒加载单例 Bean
遍历beanDefinitionMap,过滤掉原型、懒加载、抽象类,对剩余非懒加载单例 Bean 调用getBean()完成实例化,是 IOC 容器最核心、耗时最长的步骤。
二、AOP 面向切面编程
1. 核心思想
横向代码抽取:将日志、事务、权限等通用公共逻辑,从核心业务代码中剥离为独立切面;程序运行时动态将切面代码切入目标方法执行,实现解耦。
2. 两种动态代理实现
2.1 JDK 动态代理
- 原理:基于接口实现,代理类与目标类实现同一套接口;
- 限制:目标类必须至少实现一个接口;
- 特点:代理对象本质是接口的实现类。
2.2 CGLIB 动态代理
- 原理:基于继承实现,借助 ASM 字节码框架生成目标类的子类,通过重写方法完成拦截;
- 限制:目标类不能被
final修饰,拦截的方法不能为private/final; - 特点:Spring Boot 默认使用 CGLIB,无需目标类实现接口,使用范围更广。
三、Spring 事务
1. 事务传播行为
传播行为用于规定嵌套调用时事务的合并与隔离规则 。
核心规则:子方法配置优先级更高。当方法 A 调用方法 B 时,由 B 上的传播行为决定事务形态,A 的配置仅作用于自身被外部调用的场景。
常用传播行为说明:
- REQUIRED(默认):必须运行在事务中。当前存在事务则加入,无事务则新建事务。
- SUPPORTS:自适应模式。当前有事务则加入,无事务则以非事务方式执行。
- REQUIRES_NEW:强制新建独立事务。若当前存在事务,先将原有事务挂起,执行完毕后再恢复。
2. 事务常见失效场景
1. 类内部自调用
同一类中,普通方法通过 this 调用本类加了 @Transactional 的方法。
- 原因:
this调用绕过 AOP 代理,事务注解无法被拦截; - 解决方案:自我注入、使用
AopContext.currentProxy()、拆分方法到不同类。
2. 方法非 public 修饰
事务注解添加在 private、protected 或包级私有方法上。
- 原因:Spring 仅解析
public方法上的事务注解,且 CGLIB 无法重写非 public 方法。
3. 多线程异步调用
事务方法内开启新线程执行数据库操作。
- 原因:Spring 事务通过
ThreadLocal绑定数据库连接,子线程无法共享主线程的事务上下文,形成独立事务。
4. 异常被内部捕获
事务方法中使用 try-catch 捕获所有异常,未向外抛出。
- 原因:Spring 仅在检测到方法向外抛出异常时,才会触发事务回滚;异常被捕获后,容器判定执行正常,直接提交事务。