Spring 核心知识点(IOC + AOP + 事务)

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 循环依赖执行流程
  1. 实例化 ServiceA,并将其对应的工厂对象放入三级缓存;
  2. ServiceA 做属性赋值,发现依赖 ServiceB,暂停当前流程,开始创建 ServiceB
  3. 实例化 ServiceB,同样将工厂对象放入三级缓存;
  4. ServiceB 无额外依赖,依次完成属性赋值、初始化、AOP 代理生成,成为完整 Bean 并存入一级缓存,同时清理自身在二、三级缓存中的数据;
  5. 回到 ServiceA 流程,从一级缓存获取完整的 ServiceB 完成注入;
  6. ServiceA 执行后续初始化、代理生成逻辑,存入一级缓存,清理自身二、三级缓存数据。
3.3 存在 AOP 代理的循环依赖

三级缓存中存放的并非 Bean 实例,而是对象工厂(Lambda/匿名内部类),用于按需提前生成代理对象,打破 Spring「初始化完成后再创建代理」的默认规则。

java 复制代码
// 三级缓存工厂核心逻辑
getEarlyBeanReference(beanName, mbd, bean) {
    // 判断当前 Bean 是否需要生成 AOP 代理(如加了 @Transactional)
    if (需要代理) {
        // 提前创建并返回代理对象
        return createProxy(bean); 
    } else {
        // 无需代理,直接返回原始对象
        return bean; 
    }
}

执行流程:

  1. 实例化 Bean A,将对象工厂存入三级缓存;
  2. A 进行属性赋值时发现依赖 B,转而创建 B;
  3. B 实例化后发现依赖 A,一、二级缓存均未命中,触发三级缓存中的 A 对象工厂;
  4. 工厂提前为 A 生成 AOP 代理对象,并将代理对象存入二级缓存
  5. B 获取 A 的代理对象完成注入,自身正常走完生命周期,存入一级缓存;
  6. 回到 A 的流程,A 完成自身生命周期,Spring 检测到代理已提前生成,直接从二级缓存取出代理对象移入一级缓存。

补充说明:若无 AOP 代理,仅二级缓存即可解决循环依赖;引入 AOP 后,必须依靠三级缓存的对象工厂,按需提前生成代理。

4. IOC 容器完整创建流程

整体分为三大阶段:配置解析、Bean 定义注册、Bean 实例化。

阶段一:准备与解析配置
  1. prepareRefresh 容器预处理

    记录容器启动时间、更新运行状态,加载系统环境变量与配置文件(如 application.properties)。

  2. obtainFreshBeanFactory 创建并初始化 Bean 工厂

    创建底层容器 DefaultListableBeanFactory,通过配置读取器(注解/XML 读取器)扫描项目资源。

阶段二:注册 BeanDefinition
  1. 注册 BeanDefinition

    BeanDefinition 可理解为 Bean 的设计图纸 ,仅封装类名、作用域、懒加载、依赖等元数据,不会创建实例。所有 BeanDefinitionbeanName 为键,存入集合 beanDefinitionMap

  2. 执行 BeanFactory 后置处理器

    Spring 核心扩展点,可在 Bean 实例化前修改 BeanDefinition。典型应用:PropertyPlaceholderConfigurer 解析配置文件占位符。

  3. 注册 BeanPostProcessor

    仅完成接口实现类的实例化与统一存放,暂不执行逻辑,用于后续 Bean 生命周期中做拦截处理(如 AOP)。

阶段三:批量实例化单例 Bean
  1. 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 修饰

事务注解添加在 privateprotected 或包级私有方法上。

  • 原因:Spring 仅解析 public 方法上的事务注解,且 CGLIB 无法重写非 public 方法。
3. 多线程异步调用

事务方法内开启新线程执行数据库操作。

  • 原因:Spring 事务通过 ThreadLocal 绑定数据库连接,子线程无法共享主线程的事务上下文,形成独立事务。
4. 异常被内部捕获

事务方法中使用 try-catch 捕获所有异常,未向外抛出。

  • 原因:Spring 仅在检测到方法向外抛出异常时,才会触发事务回滚;异常被捕获后,容器判定执行正常,直接提交事务。
相关推荐
学计算机的计算基1 小时前
2026 年 AI 助手三国杀:Claude Code vs 腾讯马维斯 vs MiniMax Mavis,我同时用了三周,结论很意外
java·人工智能·python·算法·langchain
_Aaron___1 小时前
Spring AI 应用上线前,先把大模型调用变成可观测链路
java·人工智能·spring
小糯米6011 小时前
C语言 自定义类型:联合和枚举
java·c语言·开发语言
wei_shuo1 小时前
KES 高可用架构实战:主备复制、读写分离与容灾切换深度解析
后端
weixin_523185321 小时前
Java基础知识总结(二):JVM内存结构与变量生命周期
java·开发语言·jvm
神奇小汤圆1 小时前
沉迷 Vibe coding 后我幡然醒悟:为什么可持续开发要回归半古法编程
后端
我是大猴子1 小时前
连接池+虚拟线程
java
技术小结-李爽1 小时前
【工具】如何认识Maven
java·maven
lichenyang4531 小时前
鸿蒙电商 Demo v2:真实商品接口 + 支付/订单闭环 + 收藏功能,外加一个 ArkUI V2 @Builder 响应式断链的硬核坑
前端·后端