什么是 Spring IOC:倒过来让容器帮你 new,而不是你到处 new

我学的第一个 Spring 概念就是 IOC,当时看完定义------"控制反转(Inversion of Control),是一种设计思想,将对象的创建权交给容器管理"------完全没感觉。背是背下来了,但不知道它到底解决了什么问题。

直到我在一个项目里手动维护了几十个 Service 之间的依赖关系,一个类改动了构造函数,所有 new 的地方都得跟着改,我才真正理解 IOC 的价值。

IOC 不是什么神秘概念,它就是一句话:以前你 new 对象,现在容器帮你 new。


没有 IOC 的世界:一个改动,连锁崩溃

假设一个订单服务:

java 复制代码
public class OrderService {
    private UserService userService = new UserService();
    private InventoryService inventoryService = new InventoryService();
    private PaymentService paymentService = new PaymentService();
    
    public void createOrder(Order order) {
        User user = userService.findById(order.getUserId());
        inventoryService.deduct(order.getProductId());
        paymentService.charge(user, order.getAmount());
    }
}

看起来没什么问题。直到有一天 PaymentService 要加一个参数:

java 复制代码
public class PaymentService {
    private PaymentGateway gateway;
    
    public PaymentService(PaymentGateway gateway) {
        this.gateway = gateway;
    }
}

OrderServicenew PaymentService() 报错了------缺参数。不只是 OrderService,所有 newPaymentService 的地方都得改。项目里有十几个地方引用了它,改得我心态爆炸。

这就是 "硬编码依赖" 的代价:对象自己负责创建它所依赖的对象,创建逻辑散落在代码各处,任何变更都会引爆一堆编译错误。


IOC 怎么解决:倒过来

IOC 的思路很简单:对象不自己创建依赖,依赖由外部注入。

复制代码
没有 IOC:OrderService 自己 new UserService、new InventoryService、new PaymentService
          (谁用谁创建 → 紧耦合)

有 IOC:  IOC 容器创建 UserService、InventoryService、PaymentService
          然后把它们注入到 OrderService 里
          (容器创建、容器注入 → 松耦合)

用 Spring 改写上面那段代码:

java 复制代码
@Service
public class OrderService {
    private final UserService userService;
    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    
    // 构造函数注入:依赖由 Spring 容器在创建时传进来
    public OrderService(UserService userService, 
                        InventoryService inventoryService, 
                        PaymentService paymentService) {
        this.userService = userService;
        this.inventoryService = inventoryService;
        this.paymentService = paymentService;
    }
}

OrderService 不再关心 PaymentService 怎么创建、需要什么参数。它只声明"我需要这仨",Spring 容器负责凑齐了塞给它。PaymentService 加参数也好、改实现也好,OrderService 的代码一行不动。

这就是 IOC 的核心价值:调用方和被调用方解耦,创建逻辑集中管理。


IOC 容器是什么

Spring 的 IOC 容器本质上是一个巨大的 Map

复制代码
Map<String, Object> beanMap = new ConcurrentHashMap<>();
beanMap.put("userService", new UserService());
beanMap.put("orderService", new OrderService(userService, ...));
...

当然,实际实现比这复杂得多------有作用域管理、生命周期回调、循环依赖检测、AOP 代理等------但核心思想就是"一个帮你存对象、帮你注入依赖的 Map"。

IOC 容器的两个核心接口:

接口 定位 特性
BeanFactory 基础容器 懒加载,访问时才创建 Bean
ApplicationContext 高级容器 启动时预创建所有单例 Bean,额外提供事件发布、国际化、资源加载

面试里这两者的区别是高频考点。简单记:ApplicationContextBeanFactory 的增强版,Spring Boot 项目里默认用的就是它。


IOC 和 DI 的关系

这是一个经典的面试坑:IOC 和 DI 是一回事吗?

不是。IOC 是思想 (控制权反转),DI 是实现方式(依赖注入)。

复制代码
IOC(控制反转):对象的创建权从"调用方"转移到了"容器"
                 ↓ 怎么实现?
DI (依赖注入):容器在创建对象时,把它的依赖"注入"进去
                 ↓ 怎么注入?
      构造函数注入 / Setter 注入 / 字段注入

类比一下:IOC 是"我不开车了,我打车"(控制权从你这儿交给了平台),DI 是"平台派了辆出租车接你"(具体的实现方式)。


三种注入方式

方式 写法 优点 缺点
构造函数注入 public X(A a, B b) {...} 强依赖一目了然、不可变(final)、Spring 官方推荐 参数多时构造函数很长
Setter 注入 setA(A a) {...} 可选依赖、可后续修改 依赖不明确、可能 NPE
字段注入 @Autowired private A a; 代码最简洁 无法测试、隐藏依赖、@Autowired 被标记为不推荐

我最开始写 Spring 全用 @Autowired 字段注入------代码少,看着清爽。后来写单元测试时发现没法 mock 依赖,因为依赖是通过反射注入的私有字段。改造成构造函数注入后,测试直接传 mock 进去就行了。现在我的写法是:必选依赖用构造注入 + final,可选依赖用 Setter。


Bean 在 IOC 容器里的一生

一个 Bean 从定义到被销毁,走的是一条完整的生命周期链:

复制代码
1. 实例化 (instantiate)
   ↓
2. 属性赋值 (populate)
   ↓
3. BeanNameAware / BeanFactoryAware (回调感知)
   ↓
4. BeanPostProcessor.postProcessBeforeInitialization() (前置处理)
   ↓
5. @PostConstruct / InitializingBean.afterPropertiesSet() (初始化)
   ↓
6. BeanPostProcessor.postProcessAfterInitialization() (后置处理 → AOP 在这里生成代理)
   ↓
7. Bean 就绪,存入容器
   ↓
8. @PreDestroy / DisposableBean.destroy() (销毁)

我踩过一个坑:在 @PostConstruct 方法里调用了另一个 @PostConstruct 还没执行完的 Bean,结果拿到的是个半成品------属性还没注入完。原因就是没搞清楚 @PostConstruct 的执行顺序不是按依赖优先级来的。从那以后,初始化的跨 Bean 依赖全部放到 ApplicationListener<ContextRefreshedEvent> 里,等所有 Bean 就绪了再干活。


面试高频追问

Q:IOC 解决了什么问题?

三个:① 解耦(调用方不直接 new 依赖),② 集中管理(所有对象的创建和生命周期在容器里),③ 便于测试(可以注入 mock 对象)。说白了就是把到处散落的 new 收拢到一个地方。

Q:Spring 怎么知道哪些类要放进 IOC 容器?

四个方式:XML 配置(<bean> 标签)、@Component / @Service / @Repository / @Controller 注解、@Configuration + @Bean 方法、以及 Spring Boot 的自动配置(@EnableAutoConfiguration 扫描 spring.factories)。

Q:循环依赖怎么解决?

Spring 用三级缓存 解决构造注入之外的循环依赖:singletonObjects(成品)、earlySingletonObjects(半成品)、singletonFactories(工厂)。A 依赖 B、B 依赖 A 时:A 创建后暴露出半成品引用 → B 创建时拿到 A 的半成品 → B 完成后注入到 A → A 完成。但构造注入的循环依赖无法解决,会抛 BeanCurrentlyInCreationException

Q:单例 Bean 的线程安全问题?

IOC 容器不保证线程安全。单例 Bean 意味着只有一个实例,多线程同时操作时,如果 Bean 内部有可变状态(成员变量),就会出现线程安全问题。所以 Controller / Service 里不要定义可变成员变量,状态放方法局部变量或用 ThreadLocal


IOC 容器内部的组织方式

Spring IOC 容器的内部结构可以简化为:

复制代码
ApplicationContext
  └── BeanFactory (DefaultListableBeanFactory)
        ├── beanDefinitionMap: Map<String, BeanDefinition>   (Bean 的定义信息)
        ├── singletonObjects:   Map<String, Object>          (成品单例 Bean)
        ├── earlySingletonObjects: Map<String, Object>       (早期半成品,解决循环依赖)
        └── singletonFactories: Map<String, ObjectFactory>   (Bean 工厂,创建半成品)

BeanDefinition 里记录的不是对象本身,而是"这个 Bean 怎么创建"------类名、作用域、依赖关系、初始化方法、是否懒加载、@Value 的默认值等。容器启动时先解析所有 BeanDefinition,再按依赖顺序逐个创建。


说到底,IOC 就是一句话:对象不再自己管自己的依赖,而是声明"我需要什么",让容器替你凑齐了送过来。 你从一个到处 new 的包工头,变成了一个填清单等快递的甲方。这个设计思想贯穿了整个 Spring 生态------Spring MVC、Spring Boot、Spring Cloud------全部建立在 IOC 之上。理解了 IOC,Spring 就入门了一半。

相关推荐
AutumnWind04201 小时前
【JDK动态代理源码梳理】
java·后端·spring
暗夜猎手-大魔王1 小时前
转载--Hermes Agent 10 | 7 层安全防线:从用户授权到输入净化
java·数据库·安全
idolao3 小时前
Oligo 7.60 安装教程:引物设计+Java 环境配置
java·开发语言
做个文艺程序员6 小时前
第04篇:K8s 弹性伸缩实战:HPA、VPA、KEDA——Java SaaS 应对流量洪峰的秘密武器
java·容器·kubernetes·弹性伸缩·自动扩容·ai 推理伸缩
石山代码10 小时前
ArrayList / HashMap / ConcurrentHashMap
java·开发语言
AskHarries11 小时前
系统提示词、开发者指令和用户输入的优先级
java·前端·数据库
daidaidaiyu12 小时前
ThingsBoard 规则链系统源码分析和自定义定时器
java
小毛驴85012 小时前
spring-boot-maven-plugin,maven-compiler-plugin 功能对比
java·python·maven