一句话总结
IoC(控制反转):把对象创建和依赖管理的控制权从程序员交给 Spring 容器。程序员只需声明依赖关系,容器负责注入。DI(依赖注入)是 IoC 的实现方式,有三种:构造器注入(推荐,不可变、NPE 安全)、Setter 注入(可选依赖)、字段注入(@Autowired,简洁但不利于测试)。容器核心接口:BeanFactory(基础,懒加载)→ ApplicationContext(高级,支持国际化、事件、AOP)。
初级理解
什么是 IoC?
# 没有 IoC:自己 new 对象
public class UserService {
private UserDao userDao = new UserDaoImpl(); // 硬编码
}
# 有了 IoC:容器管理依赖
@Service
public class UserService {
@Autowired
private UserDao userDao; // 容器注入
}
# IoC 的好处
# 1. 解耦:UserService 不依赖具体实现
# 2. 易测试:可以注入 Mock 对象
# 3. 易扩展:切换实现只需改配置
三种注入方式
# 1. 构造器注入(推荐)
@Service
public class UserService {
private final UserDao userDao;
public UserService(UserDao userDao) { // 构造器
this.userDao = userDao;
}
}
# 优点:不可变(final)、NPE 安全、利于测试
# 2. Setter 注入
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
# 适用:可选依赖
# 3. 字段注入(不推荐)
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
# 缺点:不利于测试、隐藏依赖、可能 NPE
中级深入
@Autowired vs @Resource
| 对比维度 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring 提供 | JDK 提供(javax.annotation) |
| 注入规则 | 默认 byType,配合 @Qualifier byName | 默认 byName,找不到再 byType |
| required | 支持 required=false | 不支持 |
| 作用位置 | 构造器、字段、方法、参数 | 字段、方法 |
# @Autowired 配合 @Qualifier
@Autowired
@Qualifier("userDaoImpl") // 指定 bean name
private UserDao userDao;
# @Resource 指定 name
@Resource(name = "userDaoImpl")
private UserDao userDao;
BeanFactory vs ApplicationContext
| 对比维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 加载方式 | 懒加载(使用时才创建) | 预加载(启动时创建所有单例 Bean) |
| 国际化 | 不支持 | 支持 MessageSource |
| 事件发布 | 不支持 | 支持 ApplicationEvent |
| AOP | 需手动注册 | 自动集成 |
| 使用场景 | 资源受限环境 | 常规应用(99% 场景) |
高级拓展
循环依赖 — 三级缓存
# 三级缓存
# 一级:singletonObjects(完全初始化好的 Bean)
# 二级:earlySingletonObjects(早期引用,未完成属性填充)
# 三级:singletonFactories(Bean 工厂,可生成代理对象)
# 解决流程(A 依赖 B,B 依赖 A)
# 1. 创建 A → 放入三级缓存(singletonFactories)
# 2. 填充 A 的属性 B → 发现 B 不存在 → 创建 B
# 3. 创建 B → 放入三级缓存
# 4. 填充 B 的属性 A → 从三级缓存获取 A 的早期引用 → 注入
# 5. B 初始化完成 → 放入一级缓存
# 6. A 拿到 B → A 初始化完成 → 放入一级缓存
# 为什么需要三级缓存?
# 二级缓存存的是原始对象
# 三级缓存存的是 ObjectFactory,可以返回代理对象
# 如果 A 需要 AOP 代理,三级缓存能保证 B 注入的是代理后的 A
注意:构造器注入无法解决循环依赖(因为构造器调用时对象还未创建),只有 Setter/字段注入可以。
实战场景
场景:多个实现类如何注入?
# 接口 + 多个实现
public interface PayService {
void pay();
}
@Service("alipay")
public class AlipayService implements PayService { }
@Service("wechat")
public class WechatService implements PayService { }
# 方式1:@Qualifier 指定
@Autowired
@Qualifier("alipay")
private PayService payService;
# 方式2:@Primary 标记首选
@Service
@Primary
public class AlipayService implements PayService { }
# 方式3:注入 Map/List
@Autowired
private Map<String, PayService> payServiceMap;
// key = bean name, value = bean 实例
@Autowired
private List<PayService> payServiceList;
// 所有实现类
面试模拟
面试官:什么是 IoC?有什么好处?
你:IoC 即控制反转,把对象创建和依赖管理的控制权交给容器。好处:解耦(不依赖具体实现)、易测试(可注入 Mock)、易扩展(切换实现只需改配置)、统一管理(单例、生命周期)。
面试官:@Autowired 和 @Resource 有什么区别?
你:@Autowired 是 Spring 的,默认按类型注入,配合 @Qualifier 按名称;@Resource 是 JDK 的,默认按名称注入,找不到再按类型。@Autowired 支持 required=false,@Resource 不支持。