IoC(Inversion of Control,控制反转)是 Spring 的核心机制。它把"创建和管理对象"的控制权从应用代码交给 Spring 容器,应用代码只需声明依赖,容器负责提供。
IoC 的具体实现方式叫 DI(Dependency Injection,依赖注入),容器通过构造器、字段或 Setter 方法将依赖对象注入进来。
IoC 解决了什么问题
传统写法中,对象自己负责创建依赖:
// 传统写法:自己创建依赖,紧耦合
public class OrderService {
private UserDao userDao = new UserDaoImpl(); // 自己 new,换实现要改代码
private EmailService email = new EmailService("smtp.example.com", 465);
}
这带来三个问题:
- 紧耦合:
OrderService直接依赖具体实现类,换实现必须修改源码 - 难以测试:无法替换成 Mock 对象,单元测试困难
- 对象生命周期散乱:每次
new都产生新对象,无法复用单例
IoC 写法:
// IoC 写法:声明需要什么,容器负责提供
@Service
public class OrderService {
@Autowired
private UserDao userDao; // 容器注入,不关心具体实现
@Autowired
private EmailService emailService;
}
切换实现时只需修改容器配置,OrderService 代码本身不用动。
Spring IoC 容器
Spring 提供两个核心容器接口:
| 接口 | 说明 |
|---|---|
BeanFactory |
IoC 容器最基础的接口,懒加载 Bean |
ApplicationContext |
BeanFactory 的扩展,支持事件、国际化、AOP 等,是实际使用的容器 |
Spring Boot 启动时,@SpringBootApplication 会自动创建并启动一个 AnnotationConfigServletWebServerApplicationContext(Web 应用)或 AnnotationConfigApplicationContext(非 Web),这就是 IoC 容器的实体。
容器启动过程:
扫描 @Component/@Service/@Repository 等注解
→ 读取 @Configuration 类中的 @Bean 定义
→ 实例化所有单例 Bean
→ 注入依赖(@Autowired)
→ 执行 @PostConstruct 初始化方法
→ 容器就绪,应用可以处理请求
Bean 的注册方式
方式 1:注解扫描(最常用)
@Service // 等价于 @Component,语义上表示业务服务层
public class UserService { ... }
@Repository // 表示数据访问层,附加异常转换功能
public class UserDao { ... }
@Component // 通用组件,不属于特定层
public class IdGenerator { ... }
Spring Boot 默认扫描主类所在包及其子包下的所有 @Component(及派生注解)。
方式 2:@Bean 方法
适合注册第三方库的类(无法在源码上加注解):
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost/mydb");
return ds;
}
}
方式 3:XML 配置(旧项目)
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
</bean>
现代项目几乎不再使用,了解即可。
依赖注入的三种方式
字段注入(不推荐)
@Service
public class OrderService {
@Autowired
private UserDao userDao; // 简洁,但无法写单元测试,也无法声明 final
}
构造器注入(推荐)
@Service
public class OrderService {
private final UserDao userDao;
// Spring 4.3+ 单构造器时 @Autowired 可省略
public OrderService(UserDao userDao) {
this.userDao = userDao;
}
}
优点:依赖不可变(final),对象创建后状态完整,便于单元测试(直接 new 传入 Mock)。推荐使用构造器注入,Lombok 的 @RequiredArgsConstructor 可以消除样板代码。
Setter 注入(可选依赖场景)
@Service
public class NotificationService {
private EmailService emailService;
@Autowired(required = false) // 可选依赖,没有时不报错
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
@Autowired 与 @Qualifier
当容器中同一类型有多个 Bean 时,@Autowired 按类型匹配会失败,需要用 @Qualifier 指定 Bean 名称:
@Configuration
public class DataSourceConfig {
@Bean("primaryDs")
public DataSource primaryDataSource() { ... }
@Bean("replicaDs")
public DataSource replicaDataSource() { ... }
}
@Service
public class UserService {
@Autowired
@Qualifier("primaryDs") // 指定注入哪个 Bean
private DataSource dataSource;
}
@Value:注入配置值
@Service
public class PayService {
@Value("${pay.timeout:30}") // 读取配置,默认值 30
private int timeout;
@Value("${pay.api-key}") // 没有默认值,缺少时启动报错
private String apiKey;
}
对应 application.yml:
pay:
timeout: 60
api-key: sk-xxxxxxxx
Bean 的作用域
| 作用域 | 说明 |
|---|---|
singleton(默认) |
整个容器只有一个实例,所有注入点共享 |
prototype |
每次注入/获取都创建新实例 |
request |
每个 HTTP 请求一个实例(Web 应用) |
session |
每个 HTTP Session 一个实例(Web 应用) |
@Component
@Scope("prototype")
public class ReportGenerator { ... }
绝大多数 Bean 用默认的 singleton 即可,prototype 适合有状态的、不可复用的对象。
Bean 的生命周期
@Component
public class CacheManager {
@PostConstruct // 依赖注入完成后执行,用于初始化
public void init() {
System.out.println("缓存初始化...");
}
@PreDestroy // 容器关闭前执行,用于释放资源
public void destroy() {
System.out.println("缓存清理...");
}
}
与 AOP 的关系
IoC 容器负责管理对象,AOP 负责增强对象行为,两者配合:
- 容器创建 Bean 后,如果该 Bean 匹配 AOP 切面,容器会将其替换为代理对象再注入
- 所以
@Autowired注入的实际上可能是代理对象,而非原始类的实例 - 这也是为什么
@Transactional、@Cacheable等 AOP 注解必须加在 Spring 管理的 Bean 上才生效