2、Spring 核心容器:IoC 与 DI 的实现原理

以下是为你精心撰写的 《2.1 Spring 核心容器:IoC 与 DI 的实现原理》 深度说明文档,系统性地剖析 Spring 如何通过 反射、注解解析、依赖注入,将一堆普通的 Java 对象(POJO)组装成一个可运行、可管理、可扩展的企业级应用系统。

本章内容是理解 Spring 的基石,也是面试高频考点。你将不仅“会用”,更能“知其所以然”。


📜 2.1 Spring 核心容器:IoC 与 DI 的实现原理

目标:深入理解 Spring 容器如何通过反射、注解解析与依赖注入,将 POJO 组装成可运行系统


✅ 一、IoC 容器:Spring 的心脏 —— BeanFactoryApplicationContext

1.1 什么是 IoC 容器?

IoC(Inversion of Control,控制反转)是一种设计思想,其核心是:对象的创建与依赖关系的管理,不再由程序代码主动控制,而是交给外部容器来完成

在 Spring 中,这个“外部容器”就是 IoC 容器。它负责:

  • 读取配置(XML / 注解 / Java 配置)
  • 实例化 Bean
  • 注入依赖
  • 管理生命周期
  • 提供 AOP、事务、事件等扩展能力

1.2 BeanFactory vs ApplicationContext:谁才是真正的“主角”?

特性BeanFactoryApplicationContext
定位最基础的 IoC 容器企业级容器,扩展自 BeanFactory
加载时机懒加载(第一次 getBean 时才初始化)预加载(启动时初始化所有 singleton Bean)
功能仅提供基本的 getBean、依赖注入支持:AOP、事件发布、国际化、资源加载、Web 环境集成
性能轻量,内存占用少较重,但功能完整
使用场景嵌入式系统、资源受限环境绝大多数企业项目(推荐)
典型实现DefaultListableBeanFactoryClassPathXmlApplicationContext, AnnotationConfigApplicationContext

结论
ApplicationContextBeanFactory 的超集,是 Spring 开发的默认选择
除非你明确需要极致轻量(如 Android、嵌入式设备),否则永远使用 ApplicationContext

1.3 源码视角:容器如何启动?

// 1. 使用注解配置启动容器
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

// 2. 底层调用链(简化):
// AnnotationConfigApplicationContext → refresh() → 
//   → prepareRefresh() → obtainFreshBeanFactory() → 
//   → loadBeanDefinitions() → scanPackages() → 
//   → registerBeanDefinitions() → 
//   → doCreateBean() → createBeanInstance() → populateBean() → initializeBean()

💡 关键点ApplicationContextrefresh() 方法是整个容器启动的“总开关”,它触发了从配置扫描 → Bean 定义注册 → 实例化 → 初始化的完整流程。


✅ 二、Bean 的生命周期:从“无”到“有”,再到“灭”

Spring 容器管理的每个 Bean 都经历一个清晰的生命周期。理解它,是掌握自动装配、AOP、自定义扩展的前提。

2.1 Bean 生命周期七阶段(按顺序)

阶段说明关键接口/注解
1. 实例化(Instantiation)容器通过反射调用构造器创建 Bean 实例Constructor
2. 属性赋值(Populate Properties)容器根据 @Autowired@Value、XML 配置注入依赖@Autowired, @Value, setXxx()
3. 设置 Bean 名称(Set Bean Name)若 Bean 实现 BeanNameAware,则注入其名称setBeanName(String name)
4. 设置 Bean 工厂(Set Bean Factory)若实现 BeanFactoryAware,注入容器本身setBeanFactory(BeanFactory bf)
5. 初始化前(Post-Process Before Initialization)执行 BeanPostProcessor.postProcessBeforeInitialization()BeanPostProcessor
6. 初始化(Initialization)执行初始化方法@PostConstruct, InitializingBean.afterPropertiesSet(), init-method
7. 初始化后(Post-Process After Initialization)执行 BeanPostProcessor.postProcessAfterInitialization()BeanPostProcessor
8. 使用中(Ready)Bean 可被其他 Bean 或业务代码调用
9. 销毁前(Pre-Destruction)执行销毁前回调@PreDestroy, DisposableBean.destroy(), destroy-method
10. 销毁(Destruction)容器关闭时释放资源

生命周期图示(推荐记忆顺序):

实例化 → 属性注入 → 设置名称/工厂 → 初始化前 → 初始化 → 初始化后 → 使用 → 销毁前 → 销毁

2.2 初始化与销毁的三种方式对比

方式优点缺点推荐度
@PostConstruct / @PreDestroyJSR-250 标准,语义清晰,不依赖 Spring API需要引入 javax.annotation(Java 11+ 需额外依赖)⭐⭐⭐⭐⭐(首选
InitializingBean / DisposableBeanSpring 原生,无需注解侵入性强,耦合 Spring API⭐⭐(不推荐)
XML 配置 init-method / destroy-method灵活,适用于非注解项目配置分散,维护困难⭐⭐⭐(XML 项目可用)
✅ 示例:三种方式实现同一个初始化逻辑
@Component
public class UserService implements InitializingBean, DisposableBean {

    @PostConstruct
    public void initByAnnotation() {
        System.out.println("✅ @PostConstruct 初始化");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("✅ InitializingBean 初始化");
    }

    public void customInit() {
        System.out.println("✅ init-method 初始化");
    }

    @PreDestroy
    public void destroyByAnnotation() {
        System.out.println("✅ @PreDestroy 销毁");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("✅ DisposableBean 销毁");
    }

    public void customDestroy() {
        System.out.println("✅ destroy-method 销毁");
    }
}

⚠️ 注意:@PostConstructInitializingBean.afterPropertiesSet() 会同时执行,顺序不确定,避免重复逻辑。


✅ 三、依赖注入(DI)的三种方式:为什么推荐构造器注入?

3.1 什么是依赖注入(DI)?

DI 是 IoC 的具体实现方式:容器将 Bean 所依赖的其他对象“注入”到它内部,而不是由 Bean 自己创建或查找。

3.2 三种注入方式对比

方式示例优点缺点推荐度
构造器注入(Constructor Injection)@Autowired<br>public UserService(UserRepository repo) { this.repo = repo; }- 依赖不可变(final)
- 保证对象创建时依赖已就绪
- 易于单元测试(构造器传 Mock)
- 无循环依赖风险(Spring 会报错)
需要写构造器(IDE 可自动生成)⭐⭐⭐⭐⭐ 强烈推荐
Setter 注入(Setter Injection)@Autowired<br>public void setUserRepository(UserRepository repo) { this.repo = repo; }- 支持可选依赖
- 支持循环依赖(Spring 可解决)
- 依赖可被外部修改
- 对象可能处于“不完整”状态
⭐⭐⭐(适用于可选依赖)
字段注入(Field Injection)@Autowired<br>private UserRepository userRepository;- 最简洁,代码最少- 无法 final,对象不安全
- 无法单元测试(需反射)
- 隐藏依赖,违反“显式依赖”原则
- IDE 无法提示依赖缺失
⭐(仅限快速原型,生产环境禁止

🔥 为什么 Spring 官方推荐构造器注入?

官方立场(Spring Docs)
Constructor injection is preferred because it allows you to implement your components as immutable objects and ensures that required dependencies are not null.

  • 不可变性final 字段 + 构造器注入 = 线程安全、状态稳定
  • 强制依赖:没有依赖,Bean 就创建失败 → 开发阶段暴露问题
  • 测试友好new UserService(mockRepo) 即可测试,无需 Spring 容器
  • 符合依赖倒置原则(DIP)

💡 反例警示

@Component
public class OrderService {
    @Autowired
    private PaymentService paymentService; // ❌ 字段注入,隐藏依赖

    public void process() {
        paymentService.charge(); // 运行时可能 NPE!
    }
}

PaymentService 未正确配置,编译通过,运行崩溃 —— 这是灾难性的。


✅ 四、Bean 作用域(Scope):控制对象的“生命周期与可见性”

Spring 支持多种作用域,决定一个 Bean 在容器中是“单例”还是“每次请求都新建”。

作用域说明适用场景默认
singleton容器中只有一个实例,所有请求共享服务类、工具类、DAO、Repository✅ 默认
prototype每次 getBean()新建一个实例有状态的 Bean(如 Session、临时对象)
request每个 HTTP 请求创建一个实例Web 环境下,存储请求级数据(如用户信息)
session每个 HTTP Session 创建一个实例用户登录状态、购物车
application每个 ServletContext 一个实例(Servlet 容器级别)应用级缓存、全局配置
websocket每个 WebSocket 连接创建一个实例实时通信场景(如聊天室)

✅ 使用方式:

@Component
@Scope("prototype") // 每次获取都新建
public class Cart {
    private List<Item> items = new ArrayList<>();
}

@Component
@Scope("request") // 每次 HTTP 请求新建
public class RequestContext {
    private String userId;
}

⚠️ 注意requestsession 等作用域只能在 Web 环境中使用(如 Spring MVC、Spring Boot Web),否则启动报错。

🔍 深入:prototype Bean 与 singleton 的依赖关系

@Component
@Scope("singleton")
public class OrderService {
    @Autowired
    private Cart cart; // ❌ 问题:Cart 是 prototype,但被 singleton 引用
}

⚠️ 问题OrderService 是单例,只在启动时注入一次 Cart,后续所有请求都用同一个 Cart 实例,导致数据混乱!

✅ 正确做法:使用 ObjectFactory@Lookup
@Component
@Scope("singleton")
public class OrderService {

    @Autowired
    private ObjectFactory<Cart> cartFactory; // 每次调用 get() 都新建

    public void createOrder() {
        Cart cart = cartFactory.getObject(); // ✅ 每次获取新实例
        // ...
    }
}

或使用 @Lookup(方法注入):

@Component
@Scope("singleton")
public class OrderService {

    @Lookup
    public Cart createCart() {
        return null; // Spring 会动态生成实现
    }

    public void createOrder() {
        Cart cart = createCart(); // ✅ 每次调用都返回新实例
    }
}

✅ 五、Bean 的命名与别名:如何精确控制 Bean 的“身份”

5.1 Bean 的默认命名规则

  • 若使用 @Component,默认名称是类名首字母小写

    @Component
    public class UserService { ... } // → bean name: "userService"
    
  • 若使用 @Service@Repository@Controller,规则相同。

5.2 自定义 Bean 名称

@Component("myUserService") // 自定义名称
public class UserService { ... }

// 获取时:
UserService user = context.getBean("myUserService", UserService.class);

5.3 别名(Alias):一个 Bean 多个名字

@Component("userService")
@AliasFor("myUserService")
public class UserService { ... }

或通过 Java 配置:

@Configuration
public class AppConfig {
    @Bean("userService")
    @AliasFor("userSvc")
    public UserService userService() {
        return new UserService();
    }
}

或 XML 配置:

<bean id="userService" class="com.example.UserService"/>
<alias name="userService" alias="userSvc"/>

5.4 @Qualifier:解决同类型 Bean 的歧义

问题:多个相同类型的 Bean,如何指定注入哪一个?
@Component
public class MySQLRepository implements UserRepository { ... }

@Component
public class RedisRepository implements UserRepository { ... }

@Component
public class UserService {
    @Autowired
    private UserRepository repository; // ❌ 报错:No unique bean of type 'UserRepository'
}
✅ 解决方案:@Qualifier
@Component("mysqlRepo")
public class MySQLRepository implements UserRepository { ... }

@Component("redisRepo")
public class RedisRepository implements UserRepository { ... }

@Component
public class UserService {
    @Autowired
    @Qualifier("mysqlRepo") // 明确指定
    private UserRepository repository;
}

替代方案:使用 @Primary 标记默认 Bean:

@Primary
@Component
public class MySQLRepository implements UserRepository { ... }

此时,@Autowired 会自动注入 MySQLRepository,无需 @Qualifier


✅ 六、补充:IoC 容器的实现原理(源码级理解)

6.1 核心类图(简化)

BeanFactory
  └── DefaultListableBeanFactory(核心实现)
      ├── registerBeanDefinition()       // 注册 Bean 定义
      ├── getBean()                      // 获取 Bean(触发创建)
      ├── doCreateBean()                 // 创建 Bean 实例
      │   ├── instantiateBean()          // 反射调用构造器
      │   ├── populateBean()             // 属性注入(反射 + 注解解析)
      │   └── initializeBean()           // 初始化回调
      └── getSingleton()                 // 单例缓存池

6.2 关键技术点

技术作用Spring 中的应用
Java 反射(Reflection)动态创建对象、调用方法Class.newInstance()Method.invoke()
注解解析(Annotation Processing)读取 @Component@AutowiredAnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner
字节码增强(CGLIB / JDK Proxy)创建代理对象AOP、@Async、@Transactional 的底层实现
类型匹配(Type Matching)根据类型查找 BeanAutowireCandidateResolver
缓存机制避免重复创建单例 Bean 存放在 ConcurrentHashMap<String, Object>

6.3 依赖注入的实现流程(简化)

// 1. 容器扫描包 → 找到 @Component 类 → 生成 BeanDefinition
// 2. 注册到 BeanDefinitionRegistry
// 3. 当调用 context.getBean("userService"):
//    → 检查是否已存在单例 → 否则:
//    → 1. 通过反射调用构造器 → 创建实例
//    → 2. 遍历字段/方法 → 找 @Autowired
//    → 3. 递归调用 getBean() 获取依赖
//    → 4. 调用 setter / 构造器注入依赖
//    → 5. 执行 @PostConstruct / InitializingBean
//    → 6. 放入 singletonObjects 缓存
//    → 7. 返回实例

核心思想“你只管写 POJO,剩下的交给容器”


✅ 七、最佳实践与避坑指南

主题建议
注入方式✅ 优先构造器注入,避免字段注入
作用域✅ 默认 singleton,有状态对象用 prototype,Web 用 request/session
命名✅ 使用语义化名称,避免 userServiceImpl,用 userService
@Qualifier✅ 与 @Primary 配合使用,避免过多注解
循环依赖✅ Spring 支持 setter 循环依赖(三级缓存),但构造器循环依赖会报错 → 设计上应避免
初始化顺序✅ 使用 @Order@DependsOn 控制 Bean 初始化顺序
测试✅ 使用 @Test + @ExtendWith(SpringExtension.class) + @SpringBootTest@ContextConfiguration

🚫 避坑:不要在 @PostConstruct 中使用 @Autowired 的 Bean

@Component
public class UserService {
    @Autowired
    private EmailService emailService;

    @PostConstruct
    public void init() {
        emailService.sendWelcome(); // ❌ 可能为 null!
    }
}

⚠️ 原因@PostConstruct 在属性注入之后执行,理论上应该没问题。
但若 emailService 本身是 @Lazy@Scope("prototype"),且初始化顺序异常,可能为 null。

更安全做法:使用 ApplicationListener<ContextRefreshedEvent>

@Component
public class UserService implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private EmailService emailService;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        emailService.sendWelcome(); // ✅ 容器完全启动后执行
    }
}

✅ 八、总结:IoC 容器的“魔法”本质

问题Spring 的答案
如何创建对象?使用反射 + 构造器
如何注入依赖?扫描 @Autowired + 反射调用 setter / 构造器
如何管理生命周期?通过 BeanPostProcessor 和回调接口
如何避免重复创建?单例缓存(ConcurrentHashMap
如何支持多种配置?XML / 注解 / JavaConfig → 统一转为 BeanDefinition
如何让开发者写 POJO?容器接管一切,你只需声明“我要什么”

Spring IoC 容器的本质
一个基于反射和注解解析的“对象工厂 + 依赖调度器”,它让开发者从“手动组装对象”解放出来,专注于业务逻辑。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值