2026.07.08 — ARCHIVE — 4 MIN

Spring Boot 里的 IOC 和 DI,其实没那么玄

刚学 Spring 的时候,看到 @Autowired 一脸懵,后来才明白控制反转就是把 new 对象这事外包给了容器。


Spring Boot 里的 IOC 和 DI,其实没那么玄

记得刚接触 Spring 的时候,第一反应是这东西在干嘛。

传统 Java 开发,我要用某个类,直接 new 不就完了吗?为什么要搞个什么「容器」出来,还要加各种注解?

后来学了一段时间才反应过来,原来那些注解背后藏着的是两件事:IOCDI

说白了,就是 Spring 帮我把对象的创建和管理这事给包了。


没有 Spring 的时候

回想一下,不用 Spring 的时候,代码大概长这样:

public class UserController {
    private UserService userService = new UserServiceImpl();
    
    public void getUser() {
        userService.getUser();
    }
}

看起来挺正常的对吧,我要用 UserService,就 new 一个。

但问题很快就来了。

比如,我想换个实现,改成 VIPUserService,怎么办,改代码。

比如,我想写单元测试,Mock 掉 UserService,怎么办,还是改代码。

再比如,如果 UserService 依赖了 Mapper,Mapper 依赖了 DataSource,一层层 new 下去,代码就乱成一锅粥了。

这时候你就懂了,耦合度高得离谱


有了 Spring 之后

Spring 的出现,本质上就是为了解决这个问题。

它的思路很简单:

别自己 new 了,我把对象给你创建好,你直接用就行了。

@RestController
public class UserController {

    @Autowired
    private UserService userService;
    
    public void getUser() {
        userService.getUser();
    }
}

就加了这么一行 @Autowired,Spring 会自动把我需要的 UserService 给我注入进来。

我不需要关心这个对象是怎么创建的,也不用担心它依赖了什么。

这就是 IOC,控制反转。


到底反转了什么?

听起来有点抽象,用个生活例子来说明一下。

传统方式 就像你自己做饭:

  • 自己去买菜 → 自己洗菜 → 自己炒菜 → 自己洗碗
  • 每个步骤都要自己控制

IOC 方式 就像去餐厅吃饭:

  • 你只负责点菜(声明需要什么)
  • 餐厅负责买菜、洗菜、炒菜、上菜(Spring 容器负责创建和管理对象)
  • 你不需要关心菜是怎么做的

所谓「控制反转」,反转的就是对象创建权

以前是程序员控制对象的创建(自己 new),现在是 Spring 容器控制对象的创建(自动注入)。

控制权从程序员手里,反转到了容器手里。


DI 又是什么?

经常会听到有人把 IOC 和 DI 分开讲,搞得好像两个东西一样。

其实它们说的是同一件事,只是角度不同。

  • IOC(控制反转) 是思想:对象的创建和管理权交给容器
  • DI(依赖注入) 是实现:容器自动把依赖的对象注入进来

举个更具体的例子:

@RestController
public class UserController {

    @Autowired
    private UserService userService;
}

这里的 @Autowired 就是 DI 的实现。

Spring 在启动的时候,会:

  1. 扫描到 UserServiceImpl 上的 @Service,创建这个 Bean
  2. 扫描到 UserController 上的 @RestController,创建这个 Bean
  3. 发现 UserController 中有 @AutowiredUserService
  4. 从容器中找到 UserService 类型的 Bean,注入到 UserController

整个过程对程序员透明,你只需要写注解就行。


三种注入方式,哪种最好?

Spring 提供了三种注入方式,各有各的用法。

1. 字段注入(最常用)

@Autowired
private UserService userService;

写法最简单,这也是为什么很多教程和开源项目都用这种方式。

但有个小问题:不利于单元测试。因为你没法通过构造器传入 Mock 对象。

2. 构造器注入(推荐)

private final UserService userService;

@Autowired
public UserController(UserService userService) {
    this.userService = userService;
}

或者更简洁的写法(只有一个构造器时,@Autowired 可以省略):

private final UserService userService;

public UserController(UserService userService) {
    this.userService = userService;
}

这种写法的好处是:

  • 依赖关系清晰,一眼就能看出这个类依赖了什么
  • 可以用 final 修饰,保证不可变
  • 便于单元测试

这也是官方推荐的写法。

3. Setter 注入

@Autowired
public void setUserService(UserService userService) {
    this.userService = userService;
}

可以在运行时动态修改依赖,但有个缺点:对象可能处于不完整状态(依赖还没注入就被使用)。

所以一般用得比较少。


Bean 到底是什么?

学 IOC/DI 的时候,总会听到「Bean」这个词。

其实它很简单:Bean 就是被 Spring 容器管理的对象。

以前你自己 new 出来的对象,现在变成了 Spring 帮你创建和管理的对象,所以叫 Bean。

给对象加注解,Spring 就会把它当成 Bean 管理起来:

@Service      // 业务层 Bean
public class UserServiceImpl implements UserService {
    // ...
}

@Repository   // 数据访问层 Bean
public class UserMapperImpl implements UserMapper {
    // ...
}

@Component    // 通用组件 Bean
public class JwtUtils {
    // ...
}

@Service@Controller@Repository 本质上都是 @Component 的变体。

用不同的注解是为了语义清晰,让人一看就知道这个类属于哪一层。


那第三方库的 Bean 怎么加?

有时候要用一些第三方库的对象,比如 RestTemplateObjectMapper 之类的。

这些类不是我们写的,没法加 @Component 注解,怎么办?

@Bean 注解在配置类里定义:

@Configuration
public class MyConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        return mapper;
    }
}

@Bean 用在方法上,方法名默认就是 Bean 的名称。

这样第三方对象也能被 Spring 管理了。


一个小坑:循环依赖

学 IOC 的时候,会遇到一个经典问题:循环依赖。

就是 A 依赖 B,B 又依赖 A,形成一个环:

@Service
public class A {
    @Autowired
    private B b;  // A 依赖 B
}

@Service
public class B {
    @Autowired
    private A a;  // B 依赖 A → 循环!
}

这时候 Spring 启动就会报错:

The dependencies of some of the beans in the application context form a cycle

解决方案:

  • 最好的方式是重新设计,打破循环依赖
  • 实在不行,可以用 @Lazy 延迟加载其中一个依赖
@Service
public class B {
    @Autowired
    @Lazy
    private A a;  // 延迟加载,等真正使用时再注入
}

一句话总结

IOC 和 DI 是 Spring 框架的基石,理解了它们,就理解了 Spring 的核心思想:把对象的创建和管理交给容器,程序员只关心业务逻辑。

口诀我也背过:

IOC 反转控制权,DI 注入靠容器; Component 扫组件,Bean 注解建第三方; Autowired 按类型,Resource 按名称; 构造注入最推荐,单例多例看场景。

刚开始学的时候觉得这些概念很抽象,后来自己写项目碰壁了才真正理解。

现在回头看,其实没啥大不了的,就是 Spring 帮你把 new 对象这事给包了而已。

End of plate