IoC 容器与依赖注入
问题
什么是 IoC 和 DI?Spring 的 IoC 容器是如何工作的?BeanFactory 和 ApplicationContext 有什么区别?
答案
IoC 控制反转
IoC(Inversion of Control,控制反转) 是一种设计思想:将对象的创建和依赖管理的控制权从应用代码转移到框架(容器)。
// 传统方式:对象自己管理依赖
public class UserService {
// 硬编码创建依赖 —— 耦合度高
private UserDao userDao = new UserDaoImpl();
}
// IoC 方式:容器负责创建和注入依赖
public class UserService {
private final UserDao userDao; // 不关心具体实现
// 依赖由容器注入
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
- 解耦:对象不直接创建依赖,降低耦合度
- 可测试:依赖可以被 Mock 替换
- 可扩展:切换实现只需修改配置,不改业务代码
DI 依赖注入
DI(Dependency Injection,依赖注入) 是 IoC 的具体实现方式。Spring 支持三种注入方式:
1. 构造器注入(推荐)
@Service
public class UserService {
private final UserDao userDao;
private final CacheService cacheService;
// Spring 4.3+ 单构造函数可省略 @Autowired
public UserService(UserDao userDao, CacheService cacheService) {
this.userDao = userDao;
this.cacheService = cacheService;
}
}
优点:依赖不可变(final)、对象创建时就完整、方便测试、防止循环依赖。
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; // 不推荐:无法 final,不利于测试
}
- 无法声明
final,依赖可变 - 无法在构造时验证完整性
- 单元测试时难以注入 Mock 对象(需要反射)
- 隐藏了类的依赖关系
- Spring 官方推荐构造器注入
BeanFactory vs ApplicationContext
| 对比 | BeanFactory | ApplicationContext |
|---|---|---|
| 加载方式 | 懒加载(getBean 时创建) | 预加载(启动时创建所有单例) |
| Bean 后置处理器 | 手动注册 | 自动注册 |
| 国际化 | 不支持 | 支持(MessageSource) |
| 事件机制 | 不支持 | 支持(ApplicationEvent) |
| AOP | 需手动配置 | 自动集成 |
| 常用实现 | DefaultListableBeanFactory | AnnotationConfigApplicationContext、ClassPathXmlApplicationContext |
BeanFactory 是 Spring 内部的基础设施,开发者几乎不直接使用。ApplicationContext 才是我们日常使用的 IoC 容器。
IoC 容器工作流程
Bean 的作用域
| 作用域 | 说明 | 创建时机 |
|---|---|---|
singleton(默认) | 单例,整个容器只有一个实例 | 容器启动时 |
prototype | 每次获取创建新实例 | 每次 getBean |
request | 每个 HTTP 请求一个实例 | Web 请求时 |
session | 每个 HTTP Session 一个实例 | Session 创建时 |
application | 每个 ServletContext 一个实例 | 应用启动时 |
@Component
@Scope("prototype") // 多例
public class PrototypeBean { }
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean { } // 每个请求一个实例
@Autowired 的注入过程
public interface DataSource { }
@Component
@Primary // 标记为首选
public class MysqlDataSource implements DataSource { }
@Component
public class RedisDataSource implements DataSource { }
@Service
public class UserService {
// 方式 1:@Primary 自动选择 MysqlDataSource
@Autowired
private DataSource dataSource;
// 方式 2:@Qualifier 指定名称
@Autowired
@Qualifier("redisDataSource")
private DataSource redisDs;
}
常见面试问题
Q1: 什么是 IoC?什么是 DI?
答案:
- IoC(控制反转):将对象创建和依赖管理的控制权从应用代码转移到 IoC 容器。"控制"指对象依赖的控制权,"反转"指从应用代码转移到框架。
- DI(依赖注入):IoC 的具体实现方式,容器在创建对象时自动将依赖注入进去。
IoC 是思想,DI 是实现手段。
Q2: 三种注入方式哪个最推荐?
答案:
构造器注入最推荐,原因:
- 依赖可以声明为
final,不可变 - 对象创建后就是完整的,不会出现 NPE
- 单元测试时可以直接 new 并传入 Mock 对象
- 依赖过多时构造函数参数过长,可以提示类职责过重(需要拆分)
- 能在编译期发现循环依赖
Spring 官方从 4.x 开始推荐构造器注入。
Q3: BeanFactory 和 ApplicationContext 的区别?
答案:
ApplicationContext 继承了 BeanFactory,在其基础上增加了:国际化、事件发布、资源加载、AOP 自动代理、Environment 等功能。
最大的实际差异是加载时机:BeanFactory 懒加载(用时创建),ApplicationContext 预加载(启动时创建所有单例 Bean)。预加载的好处是启动时就能发现配置错误。
Q4: @Autowired 和 @Resource 的区别?
答案:
| 对比 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring 注解 | JSR-250 标准注解 |
| 注入方式 | 先按类型,再按名称 | 先按名称,再按类型 |
| required | 支持 required=false | 不支持 |
| @Qualifier | 配合使用指定 Bean | 不需要,直接 name 属性 |
@Autowired // 按类型注入
@Resource(name = "mysqlDao") // 按名称注入
Q5: Spring Bean 的作用域有哪些?
答案:
5 种:singleton(默认,单例)、prototype(多例)、request(请求级)、session(会话级)、application(应用级)。后三种仅在 Web 应用中有效。
注意:singleton Bean 注入 prototype Bean 时,prototype Bean 不会每次创建新实例。解决方案:使用 @Lookup 注解或 ObjectProvider<T>。
Q6: Spring 是如何解决同类型多个 Bean 的注入问题的?
答案:
注入流程:按类型匹配 → 多个则按名称匹配 → 仍有多个则看 @Qualifier → 看 @Primary → 都没有则报错。
也可以注入集合:List<DataSource> 会注入所有 DataSource 类型的 Bean。
Q7: FactoryBean 和 BeanFactory 的区别?
答案:
名字相似但完全不同:
- BeanFactory:IoC 容器的顶层接口,负责管理所有 Bean
- FactoryBean<T>:一个特殊的 Bean,用于创建复杂对象。实现
getObject()方法返回实际对象
public class MyFactoryBean implements FactoryBean<MyComplexObject> {
@Override
public MyComplexObject getObject() {
// 复杂的创建逻辑
return new MyComplexObject();
}
@Override
public Class<?> getObjectType() { return MyComplexObject.class; }
}
// getBean("myFactoryBean") → MyComplexObject 实例
// getBean("&myFactoryBean") → MyFactoryBean 自身
MyBatis 的 SqlSessionFactoryBean、Dubbo 的 ReferenceBean 都是 FactoryBean 的典型应用。
相关链接
- Spring Framework - IoC 容器
- Bean 生命周期 - Bean 从创建到销毁的完整流程
- 循环依赖 - 三级缓存解决循环依赖
- AOP 面向切面编程 - Spring 另一核心功能