跳过正文
  1. 博客/

Spring IOC 容器原理

·253 字·2 分钟

Spring IOC 到底干了啥
#

学 Spring 的时候,IOC(控制反转)这个词听了不下一百遍。老师讲、教程讲、面试题也讲。但我一开始真没搞懂这玩意到底在干嘛——不就是 new 一个对象吗,为啥要搞得这么复杂?

后来写项目写多了才明白,Spring 的 IOC 容器本质上就是帮你管理对象的。你不用自己 new,它帮你 new,帮你组装,帮你销毁。听着简单,但里面门道挺多的。

Bean 生命周期——从出生到死亡
#

一个 Bean 从创建到被 GC 回收,中间经历了不少事。大致流程是这样:

  1. 实例化(调构造方法)
  2. 属性赋值(依赖注入)
  3. 初始化(各种回调)
  4. 使用
  5. 销毁

其实吧,光说这几步看着挺简单,但 Spring 在中间插了一堆扩展点。比如 BeanPostProcessor,它能在初始化前后做一些操作。AOP 的代理对象就是在这一步生成的。

我之前踩过一个坑:在构造方法里调另一个 Bean 的方法,结果拿到的是 null。debug 了半天才发现,构造方法执行的时候依赖注入还没开始呢!所以如果有初始化逻辑,老老实实用 @PostConstruct 或者实现 InitializingBean

@Component
public class MyService {
    
    @Autowired
    private UserDao userDao;
    
    @PostConstruct
    public void init() {
        // 这里才能安全地用 userDao
        userDao.loadCache();
    }
}

BeanFactory 和 ApplicationContext,到底用哪个
#

你可能会想,这俩都是 IOC 容器,有啥区别?

简单说,BeanFactory 是爹,ApplicationContext 是儿子。ApplicationContext 继承了 BeanFactory,在它基础上加了一堆功能:

  • 国际化(MessageSource
  • 事件机制(ApplicationEventPublisher
  • 资源加载(ResourceLoader
  • AOP 支持

日常开发基本不会直接碰 BeanFactory,用 ApplicationContext 就对了。

还有个重要区别:BeanFactory 是懒加载,用到 Bean 的时候才创建;ApplicationContext 默认饿加载,启动时就把单例 Bean 全创建好了。这也是为啥 Spring Boot 启动有时候比较慢——它在启动阶段把所有 Bean 都实例化了。

依赖注入的几种方式
#

Spring 的依赖注入有三种姿势:

1. 字段注入(最偷懒的写法)

@Component
public class OrderService {
    @Autowired
    private UserService userService;
}

写起来最爽,但 Spring 官方其实不推荐。为啥?因为它依赖反射,没法做 final 字段,而且单元测试的时候不好 mock。

2. Setter 注入

@Component
public class OrderService {
    private UserService userService;
    
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

好处是灵活,可以选择性注入。坏处是依赖关系不够明确。

3. 构造器注入(官方推荐)

@Component
public class OrderService {
    private final UserService userService;
    
    public OrderService(UserService userService) {
        this.userService = userService;
    }
}

构造器注入的好处:字段可以是 final 的,依赖关系一目了然,Spring 4.3 之后如果只有一个构造方法连 @Autowired 都不用写。配合 Lombok 的 @RequiredArgsConstructor 简直完美。

说到这里,我个人项目里基本都用构造器注入了。一开始觉得麻烦,用习惯之后发现代码清爽多了。

循环依赖怎么解决的
#

这是面试高频题。什么是循环依赖?就是 A 依赖 B,B 又依赖 A。

Spring 用三级缓存来解决这个问题。说实话这部分我看源码看了好几遍才搞明白。

三级缓存长这样:

  • 一级缓存(singletonObjects):完全初始化好的 Bean
  • 二级缓存(earlySingletonObjects):提前暴露的 Bean,还没完成属性注入
  • 三级缓存(singletonFactories):Bean 的工厂对象,用来生成提前暴露的引用

流程大概是这样:创建 A 的时候,先把 A 的工厂放到三级缓存。然后发现 A 依赖 B,就去创建 B。创建 B 的时候发现 B 依赖 A,就去缓存里找。三级缓存里有 A 的工厂,调用工厂方法拿到 A 的早期引用(如果需要代理就返回代理对象),放到二级缓存。B 拿到 A 的引用后完成初始化,然后 A 也能完成初始化。

有个细节:构造器注入的循环依赖 Spring 解决不了。因为三级缓存是在构造方法执行之后才放入的,构造器注入的时候对象还没创建出来,没法提前暴露。遇到这种情况要么改成 Setter 注入,要么用 @Lazy

@Component
public class A {
    private final B b;
    
    public A(@Lazy B b) {
        this.b = b; // 这里注入的其实是 B 的代理对象
    }
}

顺便提一下,Spring Boot 2.6 之后默认禁止循环依赖了。官方态度很明确:循环依赖本身就是设计问题,你应该重构代码而不是依赖框架帮你兜底。

小结
#

IOC 容器用起来简单,加个注解就行。但面试官就喜欢问底层原理。Bean 生命周期、三级缓存这些东西,建议还是自己去翻一下源码,看过一遍印象会深很多。我当时就是在 AbstractAutowireCapableBeanFactorydoCreateBean 方法上打了个断点,一步一步跟下来的,比看十篇博客都管用。