跳到主要内容

IoC 容器与依赖注入

问题

什么是 IoC 和 DI?Spring 的 IoC 容器是如何工作的?BeanFactory 和 ApplicationContext 有什么区别?

答案

IoC 控制反转

IoC(Inversion of Control,控制反转) 是一种设计思想:将对象的创建和依赖管理的控制权从应用代码转移到框架(容器)。

没有 IoC
// 传统方式:对象自己管理依赖
public class UserService {
// 硬编码创建依赖 —— 耦合度高
private UserDao userDao = new UserDaoImpl();
}
使用 IoC
// IoC 方式:容器负责创建和注入依赖
public class UserService {
private final UserDao userDao; // 不关心具体实现

// 依赖由容器注入
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
IoC 的核心价值
  • 解耦:对象不直接创建依赖,降低耦合度
  • 可测试:依赖可以被 Mock 替换
  • 可扩展:切换实现只需修改配置,不改业务代码

DI 依赖注入

DI(Dependency Injection,依赖注入) 是 IoC 的具体实现方式。Spring 支持三种注入方式:

1. 构造器注入(推荐)

ConstructorInjection.java
@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 注入

SetterInjection.java
@Service
public class UserService {
private UserDao userDao;

@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}

适用于可选依赖。

3. 字段注入(不推荐)

FieldInjection.java
@Service
public class UserService {
@Autowired
private UserDao userDao; // 不推荐:无法 final,不利于测试
}
为什么不推荐字段注入?
  1. 无法声明 final,依赖可变
  2. 无法在构造时验证完整性
  3. 单元测试时难以注入 Mock 对象(需要反射)
  4. 隐藏了类的依赖关系
  5. Spring 官方推荐构造器注入

BeanFactory vs ApplicationContext

对比BeanFactoryApplicationContext
加载方式懒加载(getBean 时创建)预加载(启动时创建所有单例)
Bean 后置处理器手动注册自动注册
国际化不支持支持(MessageSource)
事件机制不支持支持(ApplicationEvent)
AOP需手动配置自动集成
常用实现DefaultListableBeanFactoryAnnotationConfigApplicationContext、ClassPathXmlApplicationContext
实际开发中使用 ApplicationContext

BeanFactory 是 Spring 内部的基础设施,开发者几乎不直接使用。ApplicationContext 才是我们日常使用的 IoC 容器

IoC 容器工作流程

Bean 的作用域

作用域说明创建时机
singleton(默认)单例,整个容器只有一个实例容器启动时
prototype每次获取创建新实例每次 getBean
request每个 HTTP 请求一个实例Web 请求时
session每个 HTTP Session 一个实例Session 创建时
application每个 ServletContext 一个实例应用启动时
BeanScope.java
@Component
@Scope("prototype") // 多例
public class PrototypeBean { }

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean { } // 每个请求一个实例

@Autowired 的注入过程

MultipleBeansResolution.java
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: 三种注入方式哪个最推荐?

答案

构造器注入最推荐,原因:

  1. 依赖可以声明为 final,不可变
  2. 对象创建后就是完整的,不会出现 NPE
  3. 单元测试时可以直接 new 并传入 Mock 对象
  4. 依赖过多时构造函数参数过长,可以提示类职责过重(需要拆分)
  5. 能在编译期发现循环依赖

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 的典型应用。

相关链接