回顾最初的 Spring :IOC、AOP 与 Bean 生命周期

一、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 的整个生命周期。

生命周期包括:

  1. 实例化:对象被创建(相当于在堆内存 new 了一个对象)
  2. 属性注入:属性或依赖的 Bean 被注入(此时对象已经创建完成)
  3. 感知接口调用:如 ApplicationContextAware
  4. 初始化:这个初始化是从 Spring 的角度来看:
    1. @PostConstruct
    2. 实现 InitializingBean 接口
    3. 自定义 init-method
    4. BeanPostProcessor 的前置后置处理
  5. 销毁:
    1. @PreDestroy
    2. 实现DisposableBean
    3. 自定义 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
没有循环依赖时的创建流程
  1. getBean("A") -> 实例化 A,将工厂函数放到三级缓存
  2. 属性注入
  3. 初始化阶段(调用@PostConstruct / init-method)
  4. 判断是否需要增强,如果有 AOP,生成代理对象
  5. 成品 A 对象(或者代理 A 对象)放入一级缓存,清理临时缓存

    虽然 Spring 也会把 ObjectFactory 放到三级缓存中,但由于创建过程中没有其他对象引用A,所以这个工厂函数不会被调用,最终只会放入一级缓存,二级 、三级缓存不会真正生效。

    循环依赖中的流程

    以 A 和 B 的相互依赖为例:

    1. getBean("A")  -> 实例化 A -> 将生产代理的工厂函数放到三级缓存
    2. 注入 A 的属性 -> 发现需要 B -> getBean("B")
    3. 创建 B -> 实例化对象,放入三级缓存
    4. 注入 B 的属性 -> 发现需要 A -> 从三级缓存中获取 A 的工厂函数,生成 A 的代理对象 -> 放入二级缓存
    5. 注入 B.a = 代理A 对象,B 对象创建完成 -> 放入一级缓存
    6. 回到 A,完成属性注入 -> 将最终的 A 对象放入一级缓存
     为什么需要三级缓存?

    如果只有二级缓存,在刚才的第4步中,只能把没有增强的 A 原始对象给到 B,才能完成 B 对象的创建

    在 AOP 场景下(如 A 使用 @Transactional),需要的是代理增强对象,将 A 原始对象暴露出去,会导致事务不生效。

    三级缓存的引入,本质是为了解决 循环依赖 + AOP 的冲突问题

    其他问题

    构造器注入无法解决循环依赖,因为构造阶段还没有机会提前暴露对象

    五、小结

    • IOC:让 Spring 管理对象,依赖注入
    • AOP:让 Spring 实现自动植入逻辑,提供可维护性
    • 生命周期管理:让 Spring 接管对象的创建,初始化和销毁,并提供钩子增强能力
    • 三级缓存:在循环依赖中,Spring 通过三级缓存,保证提前暴露出去的是正确的代理 Bean
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值