🏗️ IoC 容器
IoC/DI 本质 · BeanFactory vs ApplicationContext · 依赖注入方式 · Bean 作用域
1. 什么是 IoC 和 DI?为什么要反转控制?
IoC(控制反转):对象创建和依赖管理的控制权从"对象自己"反转给"容器"(Spring IoC 容器)。你不再 new 依赖,而是声明需求,容器负责注入。
DI(依赖注入):IoC 的具体实现方式——容器在创建 Bean 时,把依赖的 Bean 自动注入进来(构造器/Setter/字段)。
为什么要反转:
- 解耦:类只依赖接口(UserService 依赖 UserMapper 接口),不依赖具体实现类 → 替换实现零改动
- 可测试性:测试时可以注入 Mock 实现
- 统一生命周期管理:单例、代理、事务都由容器统一掌控
反转前 vs 反转后
// ❌ 传统:自己 new,耦合具体实现,难以替换和测试
class OrderService {
private final OrderMapper mapper = new OrderMapperImpl();
}
// ✅ IoC:声明依赖,由容器注入(构造器注入,推荐)
@Service
class OrderService {
private final OrderMapper mapper;
@Autowired
public OrderService(OrderMapper mapper) { // 容器发现依赖并注入
this.mapper = mapper;
}
}
🎯 面试要点
- IoC 是思想(好莱坞原则:Don't call us, we'll call you),DI 是落地手段
- 构造器注入优先(Bean 可被 final 修饰、依赖完整、防循环依赖告警)——Spring 官方推荐
- @Autowired 按类型注入;有多个候选时报错 → 用 @Qualifier("name") 或 @Resource(name=) 指定
2. BeanFactory 和 ApplicationContext 的区别?
- BeanFactory:IoC 容器的最底层接口,定义 Bean 的延迟加载(getBean 时才实例化)
- ApplicationContext:BeanFactory 的超集,启动时预加载单例 Bean(除非配置 lazy-init),并扩展了:
- 国际化(MessageSource)
- 事件发布机制(ApplicationEventPublisher)
- 资源加载(ResourcePatternResolver,classpath: 前缀)
- 环境抽象(Environment:properties、profiles)
Spring Boot Web(Servlet) 环境默认是 AnnotationConfigServletWebServerApplicationContext,Reactive 是 AnnotationConfigReactiveWebServerApplicationContext,只有非 Web 应用才是 AnnotationConfigApplicationContext。
🎯 面试要点
- 延迟加载 vs 预加载:预加载能启动时暴露配置错误,延迟加载省启动时间
- 懒加载注解 @Lazy:仅对个别重 Bean 使用,别全局用
- 面试延伸:DefaultListableBeanFactory 是所有容器的"地基"(BeanDefinition 注册中心)
3. 依赖注入的几种方式?@Autowired 和 @Resource 的区别?
三种注入方式:
- 构造器注入(推荐):依赖不可变、可空检查、易测试。Spring 4.3+ 单构造器可省略 @Autowired
- Setter 注入:可选依赖、可重新赋值
- 字段注入(@Autowired 直接注解字段):写法最简,但无法 final、反射注入、测试需要反射改值——不推荐
@Autowired vs @Resource:
| 对比 | @Autowired(Spring) | @Resource(JSR-250) |
|---|---|---|
| 匹配方式 | 按类型,多候选再按名称 | 先按名称,找不到再按类型 |
| 指定候选 | @Qualifier | name 属性 |
| 作用范围 | 字段/构造器/方法 | 字段/Setter |
| 必填控制 | required=false 可设为可选 | 必填 |
🎯 面试要点
- 多实现注入:List/Map 批量注入所有候选(如策略模式注册表);@Primary 指定默认
- 字段注入的危害:循环依赖掩盖(构造器注入循环会直接报错,字段注入悄悄成立延迟到使用时才炸)
- JavaConfig 推荐 @Bean 方法 + 构造器注入
4. Bean 有哪几种作用域?
- singleton(默认):全容器一个实例。Spring 管理的单例与线程安全无关——Bean 本身要自己保证线程安全(无状态 Bean 天然安全;有状态字段要小心)
- prototype:每次 getBean/注入都是新实例,容器不负责其生命周期(destroy 回调不执行)
- request:每个 HTTP 请求一个实例(Web 环境)
- session:每个会话一个实例(Web 环境)
- application / websocket:ServletContext / WebSocket 级单例
经典坑:单例注入原型。singleton 依赖 prototype 时,注入发生在创建时——原型 Bean 只创建一次,之后每次用的是同一个。解决:
- 注入
ObjectFactory<T>或Provider<T>,每次使用调 get() - @Lookup 方法(CGLIB 子类化,方法每次返回新实例)
- 把作用域改 request/proxyMode(@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS))
🎯 面试要点
- Spring 的单例容器本身线程安全(Bean 注册表),但 Bean 的共享状态需要自己加锁/无状态化
- Service/Mapper 等无状态 Bean 用 singleton 没问题;带可变状态的对象(计数器、Session)别放单例
- @Scope("prototype") 时注意:依赖它的单例拿不到新实例(见上)