Spring IoC 容器核心知识点详解——从注解到容器启动全流程实战
一、前言:什么是 IoC?为什么要用 Spring?
1.1 传统开发的痛点
在没有 Spring 的时代,我们创建对象需要手动 new:
问题:
- 对象之间高度耦合,更换实现类需要修改代码
- 对象的生命周期全靠程序员手动管理
- 无法灵活配置参数,硬编码严重
1.2 IoC 思想
IoC(Inversion of Control,控制反转):对象的创建和依赖关系的维护,交给 Spring 容器来管理,而不是在代码中手动 new。
- DI(Dependency Injection,依赖注入) 是实现 IoC 的核心方式
- 容器"主动"将依赖的 Bean 注入到需要它的地方
- 开发者只需要声明"我需要什么",容器负责"怎么给你"
1.3 本项目的技术栈
| 技术 | 版本 | 作用 |
|---|---|---|
| Spring Context | 5.3.20 | IoC 容器核心 |
| Lombok | 1.18.44 | 简化样板代码 |
| JUnit | 3.8.1 | 单元测试 |
| Maven | - | 项目构建 |
二、项目整体结构
2.1 目录结构
Spring-Ioc/
├── src/main/java/org/example/
│ ├── MainApplication.java // 启动入口
│ ├── config/ // 配置类
│ │ ├── AppConfig.java // 主配置类
│ │ ├── DataSourceConfig.java // 数据源配置(@Bean)
│ │ └── AppProperties.java // 属性注入演示
│ ├── dao/ // 数据访问层
│ │ ├── UserDao.java // DAO接口
│ │ ├── UserDaoMysqlImpl.java // MySQL实现(@Primary)
│ │ ├── UserDaoOracleimpl.java // Oracle实现
│ │ └── OldUserDao.java // 演示@PostConstruct/@PreDestroy
│ ├── service/ // 业务层
│ │ ├── UserService.java // 构造器注入 + @Qualifier
│ │ ├── CollectionInjectService.java // 集合注入(List/Map)
│ │ ├── OrderService.java // @Lazy懒加载
│ │ └── TestService.java // @Autowired(required=false)
│ ├── factory/
│ │ └── ConnectionFactoryBean.java // FactoryBean工厂模式
│ ├── processor/
│ │ └── MyBeanPostProcessor.java // BeanPostProcessor后置处理器
│ └── condition/
│ └── matches.java // 条件装配Condition
├── src/main/resources/
│ └── app.properties // 外部配置文件
└── pom.xml
2.2 核心业务场景
本项目模拟了一个简化的数据访问场景:
UserDao是数据访问接口,有 MySQL 和 Oracle 两种实现UserService注入UserDao完成业务调用- 展示 Spring IoC 容器中 Bean 的注册、注入、生命周期等完整流程
三、核心知识点详解
3.1 IoC 容器的创建与启动
3.1.1 AnnotationConfigApplicationContext
Spring 提供了两种 IoC 容器实现:
ClassPathXmlApplicationContext:基于 XML 配置文件AnnotationConfigApplicationContext:基于 Java 注解(本项目使用)
启动入口代码:
// MainApplication.java
public class MainApplication {
public static void main(String[] args) {
// 1. 创建基于注解的IoC容器,并指定配置类
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("IOC容器创建成功");
// 2. 从容器中获取Bean
UserService userService = context.getBean(UserService.class);
userService.getUserInfo();
// 3. 关闭容器
context.close();
}
}
关键点:
AnnotationConfigApplicationContext(AppConfig.class):传入配置类,容器会自动扫描并注册 BeangetBean():按类型或名称获取 Beancontext.close():关闭容器,触发 Bean 的销毁生命周期
3.2 Bean 的注册方式
Spring 提供了多种注册 Bean 到容器的方式:
3.2.1 组件扫描注解(@Component / @Service / @Repository)
这是最常用的方式,通过类注解标记组件,再配合 @ComponentScan 自动扫描。
主配置类:
// AppConfig.java
@Configuration // 标记为配置类
@ComponentScan("org.example") // 自动扫描该包及子包下的所有组件
@Import({DataSourceConfig.class, AppProperties.class}) // 导入其他配置类
public class AppConfig {
// 配置类本身可以为空,主要作用是告诉Spring去哪里找Bean
}
各层组件注解使用:
// DAO层:@Repository
@Repository
public class UserDaoMysqlImpl implements UserDao { ... }
// Service层:@Service
@Service
public class UserService { ... }
// 通用组件:@Component
@Component
public class ConnectionFactoryBean { ... }
| 注解 | 作用含义 | 使用位置(场景) | 补充说明 |
|---|---|---|---|
@Component |
通用组件,把类交给 Spring IOC 容器管理 | 不属于 Controller、Service、Dao 的普通工具类、通用组件 | 另外三个注解本质就是@Component的衍生注解 |
@Service |
业务逻辑层组件 | Service 业务层,存放业务处理代码 | 语义化注解,功能等同于 @Component,只是用来标识业务层 |
@Repository |
数据访问层组件 | Mapper/DAO 层,和数据库交互 | MyBatis 开发中一般不写,Mapper 使用@Mapper注解 |
@Controller |
Web 控制器 | 页面跳转的控制器,返回视图页面 | 返回页面,不返回 JSON 数据 |
@RestController |
REST 风格控制器 | 接口开发,前后端分离项目 | @Controller + @ResponseBody组合,直接返回 JSON |
注意:它们在功能上没有本质区别,只是为了代码可读性和分层规范。
3.2.2 @Bean 手动注册
当需要注册第三方类或需要复杂初始化逻辑时,使用 @Bean:
// DataSourceConfig.java
public class DataSourceConfig {
@Value("${db.driver}") //@Value从配置文件里读取配置值,自动注入给类的成员变量
private String driver;
@Value("${db.url}")
private String url;
@Value("${db.username}")
private String username;
@Value("${db.password}")
private String password;
@Bean // 注册Bean到容器
@Scope("singleton") // 作用域:单例(默认)
public MyDataSource myDataSource() {
MyDataSource myDataSource = new MyDataSource();
myDataSource.setDriver(driver);
myDataSource.setUrl(url);
myDataSource.setUsername(username);
myDataSource.setPassword(password);
return myDataSource;
}
public static class MyDataSource {
private String driver;
private String url;
private String username;
private String password;
// getter/setter 省略...
public void connect() {
System.out.println("连接数据库: " + url);
}
public void close() {
System.out.println("关闭数据库连接");
}
}
}
注意:DataSourceConfig 没有 @Component 注解,无法被 @ComponentScan 扫描,必须通过 @Import 手动导入。
3.2.3 @Import 导入配置类
@Import({DataSourceConfig.class, AppProperties.class})
@Import 的作用:
- 导入一个或多个配置类
- 被导入的类不需要
@Component注解 - 适合导入第三方库的配置类
3.3 Bean 的作用域
通过 @Scope 注解控制 Bean 的创建策略:
| 作用域 | 说明 | 创建时机 |
|---|---|---|
singleton(默认) |
单例,容器中只有一个实例 | 容器启动时创建 |
prototype |
原型,每次获取都创建新实例 | 每次 getBean() 时创建 |
request |
Web环境,每次HTTP请求一个实例 | 请求时创建 |
session |
Web环境,每次会话一个实例 | 会话时创建 |
application |
Web环境,整个应用一个实例 | 应用启动时创建 |
@Bean
@Scope("prototype") // 每次调用都创建新的MyDataSource实例
public MyDataSource myDataSource() {
return new MyDataSource();
}
3.4 依赖注入(DI)
3.4.1 @Autowired 自动装配
@Autowired 按照类型自动装配,Spring 会自动查找容器中匹配的 Bean。
三种注入方式:
(1)构造器注入(推荐)
// UserService.java
@Service
public class UserService {
private final UserDao userDao;
@Autowired // Spring自动注入UserDao的实现类
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void getUserInfo() {
System.out.println("UserService业务层开始执行");
userDao.queryUser();
}
}
优点:
- 依赖不可变(
final修饰) - 强制注入,保证依赖不为空
- 便于单元测试
Spring 4.3+ 新特性:当类只有一个构造器时,
@Autowired可以省略。
(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;
}
3.4.2 @Qualifier 解决多 Bean 冲突
当存在多个同类型 Bean 实现时,使用 @Qualifier 指定注入哪个:
// UserService.java
@Service
public class UserService {
private final UserDao userDao;
@Autowired
public UserService(
@Qualifier("userDaoMysqlImpl") // 指定注入MySQL实现
UserDao userDao
) {
this.userDao = userDao;
}
}
Bean 的命名规则:
- 默认:类名首字母小写,如
UserDaoMysqlImpl→userDaoMysqlImpl - 自定义:
@Repository("自定义名称")
3.4.3 @Primary 优先级标记
如果某个 Bean 是默认首选注入的,可以使用 @Primary:
// UserDaoMysqlImpl.java
@Repository
@Primary // 当没有@Qualifier时,优先注入此Bean
public class UserDaoMysqlImpl implements UserDao { ... }
@Primary 与 @Qualifier 的关系:
@Primary:标记"默认选项",当没有显式指定时生效@Qualifier:精确指定,优先级高于@Primary
3.4.4 @Autowired(required = false) 允许为空
当依赖不是必须的时,可以设置 required = false:
// TestService.java
@Service
public class TestService {
@Autowired(required = false) // 如果找不到OrderService,不会报错
private OrderService orderService;
}
3.4.5 集合注入
Spring 支持自动将所有实现某接口的 Bean 注入到集合中:
// CollectionInjectService.java
@Service
public class CollectionInjectService {
// 注入所有UserDao实现的List
@Autowired
private List<UserDao> userDaoList;
// 注入所有UserDao实现的Map(key为Bean名称,value为Bean实例)
@Autowired
private Map<String, UserDao> userDaoMap;
public void printBeans() {
System.out.println("=====集合注入List=====");
userDaoList.forEach(UserDao::queryUser);
System.out.println("=====集合注入Map=====");
userDaoMap.forEach((key, value) -> {
System.out.println(key + " -> " + value);
value.queryUser();
});
}
}
运行结果:
=====集合注入List=====
查询数据库用户信息 // UserDaoMysqlImpl
查询Oracle数据库用户信息 // UserDaoOracleimpl
=====集合注入Map=====
userDaoMysqlImpl -> com.example.dao.UserDaoMysqlImpl@xxxx
userDaoOracleimpl -> com.example.dao.UserDaoOracleimpl@xxxx
3.5 Bean 的生命周期
Spring Bean 的生命周期分为三个阶段:创建 → 初始化 → 销毁。
3.5.1 完整生命周期流程
① 实例化Bean(调用构造方法)
↓
② 属性赋值(依赖注入 @Autowired)
↓
③ Aware回调(如BeanNameAware、BeanFactoryAware)
↓
④ BeanPostProcessor前置处理(postProcessBeforeInitialization)
↓
⑤ @PostConstruct 注解方法执行
↓
⑥ InitializingBean.afterPropertiesSet() 方法执行
↓
⑦ 自定义初始化方法(如 @Bean(initMethod = "init"))
↓
⑧ BeanPostProcessor后置处理(postProcessAfterInitialization)
↓
⑨ Bean就绪,可以使用
↓ →(容器关闭时)
⑩ @PreDestroy 注解方法执行
↓
⑪ DisposableBean.destroy() 方法执行
↓
⑫ 自定义销毁方法(如 @Bean(destroyMethod = "destroy"))
3.5.2 代码演示
UserDaoMysqlImpl 完整演示了生命周期的关键节点:
// UserDaoMysqlImpl.java
@Repository
@Primary
public class UserDaoMysqlImpl
implements UserDao,
InitializingBean, // 方式1:实现接口
DisposableBean { // 方式1:实现接口
@Override
public void queryUser() {
System.out.println("查询数据库用户信息");
}
// 初始化 - 方式1:实现InitializingBean接口
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("【接口】InitializingBean.afterPropertiesSet() - 初始化完成");
}
// 销毁 - 方式1:实现DisposableBean接口
@Override
public void destroy() throws Exception {
System.out.println("【接口】DisposableBean.destroy() - 销毁完成");
}
// 初始化 - 方式2:使用@PostConstruct注解(JSR-250标准)
@PostConstruct
public void annoInit() {
System.out.println("【注解】@PostConstruct - 初始化");
}
// 销毁 - 方式2:使用@PreDestroy注解(JSR-250标准)
@PreDestroy
public void annoDestroy() {
System.out.println("【注解】@PreDestroy - 销毁");
}
}
执行顺序:
【注解】@PostConstruct - 初始化 ← 先执行!
【接口】InitializingBean.afterPropertiesSet() - 初始化完成
【注解】@PreDestroy - 销毁 ← 先执行!
【接口】DisposableBean.destroy() - 销毁完成
3.5.3 三种初始化/销毁方式对比
| 方式 | 初始化注解/接口 | 销毁注解/接口 | 说明 |
|---|---|---|---|
| JSR-250注解 | @PostConstruct |
@PreDestroy |
推荐,与Spring解耦 |
| Spring接口 | InitializingBean |
DisposableBean |
与Spring耦合 |
| @Bean属性 | @Bean(initMethod) |
@Bean(destroyMethod) |
自定义方法名 |
最佳实践:优先使用 @PostConstruct / @PreDestroy,不与Spring API耦合。
3.6 延迟加载 @Lazy
默认情况下,单例 Bean 在容器启动时就会创建。使用 @Lazy 可以延迟到第一次使用时才创建:
// OrderService.java
@Service
@Lazy // 懒加载
public class OrderService {
public OrderService() {
System.out.println("OrderService构造函数被调用");
}
}
对比:
// 无@Lazy:容器启动时立即创建
// 有@Lazy:直到调用getBean(OrderService.class)时才创建
OrderService orderService = context.getBean(OrderService.class);
// 此时才打印:OrderService构造函数被调用
@Lazy 的使用场景:
- Bean 创建开销大,不想在容器启动时创建
- 解决循环依赖问题
- 非核心 Bean,希望按需加载
3.7 FactoryBean 工厂模式
FactoryBean<T> 是 Spring 提供的工厂 Bean 接口,用于创建复杂对象。
3.7.1 FactoryBean 与 BeanFactory 的区别
| 概念 | 说明 |
|---|---|
BeanFactory |
Spring IoC 容器的顶层接口,负责管理 Bean 的整个生命周期 |
FactoryBean<T> |
一个特殊的 Bean,用于创建其他 Bean,返回的是 getObject() 的结果 |
3.7.2 FactoryBean 使用示例
// ConnectionFactoryBean.java
@Component
public class ConnectionFactoryBean
implements FactoryBean<ConnectionFactoryBean.MyConnection> {
// 返回实际要注册到容器的Bean
@Override
public MyConnection getObject() throws Exception {
return new MyConnection(); // 自定义创建逻辑
}
// 返回Bean的类型
@Override
public Class<?> getObjectType() {
return MyConnection.class;
}
// 是否单例
@Override
public boolean isSingleton() {
return true; // 默认就是true
}
public static class MyConnection {
public void connect() {
System.out.println("连接数据库");
}
}
}
使用:
// 当注入ConnectionFactoryBean时,实际注入的是getObject()返回的MyConnection
@Autowired
private ConnectionFactoryBean.MyConnection myConnection; // 得到MyConnection实例
注意:如果需要获取 FactoryBean 本身,而不是其产生的对象,可以使用 & 前缀:
ConnectionFactoryBean factoryBean = context.getBean("&connectionFactoryBean");
3.8 BeanPostProcessor 后置处理器
BeanPostProcessor 是 Spring 容器的扩展点,允许在 Bean 初始化前后进行自定义处理。
3.8.1 使用场景
- Bean 的全局初始化/销毁逻辑
- AOP 的底层实现
- 日志记录、权限校验等横切关注点
- 对特定类型的 Bean 进行增强
3.8.2 代码实现
// MyBeanPostProcessor.java
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
// Bean初始化**之前**执行
@Override
public Object postProcessBeforeInitialization(
Object bean, // 当前Bean实例
String beanName // 当前Bean的名称
) throws BeansException {
System.out.println("【Before】后置处理器初始化前:" + beanName);
// 可以返回原始bean,也可以返回包装后的bean
return bean;
}
// Bean初始化**之后**执行
@Override
public Object postProcessAfterInitialization(
Object bean,
String beanName
) throws BeansException {
System.out.println("【After】后置处理器初始化后:" + beanName);
return bean;
}
}
3.8.3 执行时机
Bean实例化
↓
属性注入
↓
postProcessBeforeInitialization() ← 这里执行
↓
@PostConstruct / afterPropertiesSet()
↓
postProcessAfterInitialization() ← 这里执行
↓
Bean就绪
注意:BeanPostProcessor 会对所有 Bean 生效,不是只对某一个 Bean。
3.9 属性注入与 SpEL 表达式
3.9.1 @PropertySource 加载配置文件
// AppProperties.java
@PropertySource("classpath:app.properties") // 加载外部配置
@Configuration
public class AppProperties {
@Value("${app.project.name}") // 注入配置文件中的值
private String projectName;
// getter...
}
3.9.2 app.properties 配置文件
# 数据库连接配置
db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/spring_ioc?useSSL=false&serverTimezone=UTC
db.username=root
db.password=123456
# 应用配置
app.project.name=Spring Ioc
3.9.3 @Value 的三种用法
// 1. 注入配置文件值
@Value("${app.project.name}")
private String projectName;
// 2. SpEL 表达式 - 计算
@Value("#{10 * 2}")
private Integer num; // 结果:20
// 3. SpEL 表达式 - 调用其他Bean的方法
@Value("#{userDaoMysqlImpl.toString()}")
private String userDaoMysqlImplStr;
语法对比:
${...}:属性占位符,获取配置文件中的值#{...}:SpEL 表达式,支持复杂计算和 Bean 引用
3.10 条件装配 @Conditional
通过实现 Condition 接口,根据条件决定 Bean 是否注册到容器。
3.10.1 实现 Condition 接口
// matches.java
public class matches implements Condition {
@Override
public boolean matches(
ConditionContext context, // 条件上下文
AnnotatedTypeMetadata metadata // 注解元数据
) {
// 获取配置文件中的db.type属性,默认值为"mysql"
String env = context.getEnvironment().getProperty("db.type", "mysql");
// 只有当db.type=mysql时,条件成立
return "mysql".equals(env);
}
}
3.10.2 使用条件注解
@Service
@Conditional(matches.class) // 只有matches()返回true时才注册
public class SomeService { ... }
Spring Boot 中的自动配置就是基于此机制实现的,例如:
- 当 classpath 中有 MySQL 驱动时,自动配置 MySQL 数据源
- 当 classpath 中有 Redis 客户端时,自动配置 RedisTemplate
四、完整运行流程
4.1 启动 MainApplication
public class MainApplication {
public static void main(String[] args) {
// 1. 创建容器
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
// 2. 获取并使用Bean
UserService userService = context.getBean(UserService.class);
userService.getUserInfo();
// 3. 使用@Bean注册的数据源
DataSourceConfig.MyDataSource ds =
context.getBean(DataSourceConfig.MyDataSource.class);
ds.connect();
ds.close();
// 4. 集合注入演示
CollectionInjectService collector =
context.getBean(CollectionInjectService.class);
collector.printBeans();
// 5. 懒加载演示(第一次使用时才创建)
OrderService orderService = context.getBean(OrderService.class);
System.out.println(orderService);
// 6. 关闭容器
context.close();
}
}
4.2 预期输出顺序
IOC容器创建成功
=== BeanPostProcessor ===
【Before】后置处理器初始化前:userDaoMysqlImpl
【注解】@PostConstruct - 初始化
【接口】InitializingBean.afterPropertiesSet() - 初始化完成
【After】后置处理器初始化后:userDaoMysqlImpl
=== UserService 业务调用 ===
UserService业务层开始执行
查询数据库用户信息
=== 数据源使用 ===
数据源bean: MyDataSource{driver='com.mysql.cj.jdbc.Driver', url='jdbc:mysql://localhost:3306/spring_ioc...'}
连接数据库: jdbc:mysql://localhost:3306/spring_ioc...
关闭数据库连接
=== 集合注入 ===
=====集合注入List=====
查询数据库用户信息
查询Oracle数据库用户信息
=====集合注入Map=====
userDaoMysqlImpl -> com.example.dao.UserDaoMysqlImpl@xxxx
userDaoOracleimpl -> com.example.dao.UserDaoOracleimpl@xxxx
=== 懒加载 ===
OrderService构造函数被调用
com.example.service.OrderService@xxxx
=== 容器关闭 ===
【注解】@PreDestroy - 销毁
【接口】DisposableBean.destroy() - 销毁完成
五、知识点总结
5.1 IoC 核心流程一句话总结
开发者告诉 Spring "我需要什么"(通过注解声明),Spring 自动完成 "创建 → 注入 → 管理生命周期" 的全过程。
5.2 各知识点关联图
┌─────────────────────────────────────────────────────────────┐
│ Spring IoC 容器 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【Bean注册】 │
│ ├── @Component / @Service / @Repository (组件扫描) │
│ ├── @Bean + @Scope (手动注册+作用域) │
│ └── @Import (导入配置类) │
│ │
│ 【依赖注入】 │
│ ├── @Autowired + @Qualifier (按类型+名称) │
│ ├── @Primary (优先级标记) │
│ ├── @Autowired(required=false) (允许为空) │
│ └── List/Map 集合注入 (批量收集) │
│ │
│ 【生命周期】 │
│ ├── 实例化 → 属性注入 → 初始化 → 使用 → 销毁 │
│ ├── @PostConstruct / @PreDestroy (注解方式) │
│ └── InitializingBean / DisposableBean (接口方式) │
│ │
│ 【扩展机制】 │
│ ├── @Lazy (延迟加载) │
│ ├── FactoryBean<T> (工厂模式) │
│ ├── BeanPostProcessor (后置处理器) │
│ └── @Conditional + Condition (条件装配) │
│ │
│ 【配置管理】 │
│ ├── @PropertySource + @Value (属性注入) │
│ └── SpEL表达式 #{...} (动态计算) │
│ │
└─────────────────────────────────────────────────────────────┘
5.3 面试高频考点
| 问题 | 核心答案 |
|---|---|
| Bean 的生命周期? | 实例化→属性注入→Aware→BPP前置→@PostConstruct→InitializingBean→BPP后置→销毁 |
| @Autowired 和 @Resource 的区别? | @Autowired是Spring的,按类型注入;@Resource是JSR-250的,默认按名称注入 |
| BeanFactory 和 FactoryBean 的区别? | BeanFactory是容器接口;FactoryBean是创建Bean的工厂接口 |
| 单例 Bean 的线程安全问题? | Spring中单例Bean默认是线程安全的(无状态时);有状态时需手动处理 |
| 循环依赖怎么解决? | Spring通过三级缓存提前暴露引用,解决构造器注入外的循环依赖 |
| @Lazy 的作用? | 延迟加载,解决循环依赖,减少启动时间 |
六、结语
Spring IoC 是整个 Spring 框架的基石,理解了它,才能进一步学习 Spring AOP、Spring Boot、Spring Cloud 等高级特性。
建议学习路径:
- 动手实践:复制本项目代码,修改配置,观察运行结果
- 调试跟踪:打断点观察 Bean 的创建和注入过程
- 源码阅读:从
AnnotationConfigApplicationContext开始,逐步深入理解容器的底层实现
"IoC 不是技术,而是一种设计思想——控制权从程序代码转移到了容器。"
更多推荐
所有评论(0)