Spring IoC容器与Bean管理核心原理与实战深入全面解析
SpringIoC容器通过@Controller、@Service、@Repository、@Component、@Configuration五大类注解以及@Bean方法注解来注册和管理Bean对象,默认采用单例模式。依赖注入支持属性注入、构造方法注入和Setter注入三种方式,其中构造方法注入被推荐。当存在多个同类型Bean时,可通过@Primary、@Q
一、Bean的存储
我们先来深入探讨Bean的存储方式。上一节已经提到,若想将某个对象交由IoC容器管理,只需在类上添加@Component注解即可。不过,Spring为了更好地适配Web开发场景,还提供了更细化的注解,让不同层次的代码各司其职、清晰分工。
类注解:@Controller、@Service、@Repository、@Component、@Configuration
方法注解:@Bean
1.1 @Controller(控制器存储)
使用@Controller注解来存储Bean,操作非常直观且易于理解。

接下来,我们从Spring容器中取出这个对象,验证其存储效果。

上述代码是通过类型来查找Bean对象。但有一个常见问题:如果同一个类型在Spring容器中存在多个Bean,该如何处理?关于Bean的命名规则,上一节已经讨论过,这里不再赘述。
1.根据bean名称获取bean
Object getBean(String var1)throws BeansException;
2.根据bean名称和类型获取bean
T getBean(String var1,Class var2)throws BeansException; 3.根据类型获取bean
T getBean(Class var1)throws BeansException; 4. 按bean名称和构造函数动态创建bean,只适用于具有原型(prototype)作用域的bean
Object getBean(String var1,Class
var2)throws BeansException; 5.按bean类型和构造函数动态创建bean,只适用于具有原型(prototype)作用域的bean
T getBean(String var1,Class var2)throws BeansException;
在实际开发中,前三种方法的使用频率最高。后面的几种注解同理,我们重点展示这三种常用方式。

我们来验证一下,通过不同方式获取到的对象是否为同一个实例。

从内存地址来看,它们完全一致,说明获取到的是同一个Bean对象。这体现了Spring容器默认的单例模式,这一特性至关重要。
1.2 @Service(服务存储)
使用@Service注解存储Bean,其用法与@Controller几乎完全相同。

读取Bean的步骤也如出一辙。

1.3 @Repository(仓库存储)
@Repository顾名思义,主要用于数据访问层(持久层)。

读取Bean的方式同样一脉相承。

1.4 @Component(组件存储)
@Component是一个通用组件注解,它相当于一个基础模板,其他注解都是它的衍生品。

读取Bean的步骤保持不变。

1.5 @Configuration(配置存储)
@Configuration注解用于处理项目中的配置信息,例如数据源配置等。

读取Bean当然也没有问题。

1.6 这些注解的用途
- @Controller:控制层,负责接收请求、处理请求并返回响应。
- @Service:业务逻辑层,专门处理核心业务逻辑。
- @Repository:数据访问层,也称持久层,负责与数据库进行交互。
- @Configuration:配置层,专门处理项目中的各类配置信息。
- @Component:通用组件注解,对于不属于上述分层的普通业务类、工具类,可通过它标记,然后由Spring自动扫描并存入IoC容器。
@Controller、@Service、@Repository、@Configuration 都是@Component的衍生注解。除了@Controller不完全等同于@ResponseBody外,其余衍生注解主要起到分层语义区分的作用,底层功能其实一致——都是将类交给Spring IoC容器管理。

打个比方:杯子有喝水的杯子,也有刷牙的杯子。虽然都是杯子,但日常使用中我们更倾向于用刷牙的杯子刷牙,用喝水的杯子喝水。这些注解的角色划分,本质上也是为了让代码结构更加清晰有序。
1.7 ApplicationContext VS BeanFactory
- 从继承关系和功能来看:Spring容器有两个顶级接口:BeanFactory和ApplicationContext。BeanFactory提供最基础的容器访问能力,而ApplicationContext是它的子类,除了继承所有功能,还额外支持国际化、资源访问和事件传播等高级特性。
- 从性能方面来看:ApplicationContext倾向于一次性加载并初始化所有Bean,而BeanFactory则采用按需加载的方式,谁用谁加载,因此更加轻量。
二、方法注解@Bean
上面介绍了五大类注解,但实际开发中经常会遇到两个棘手的问题:
1. 外部包中的类,无法添加类注解
2. 一个类需要多个实例对象,例如配置多个数据源
这两个场景,就需要借助方法注解@Bean了。请注意,@Bean注解必须搭配五大类注解才能正常使用。
2.1 搭配类注解的使用


在Spring的设计中,@Bean方法注解必须配合类注解,才能将对象正确地存储到容器中。
如果把@Component注释掉,会是什么结果?

2.2 定义多个对象

2.3 重命名Bean
2.3.1 方法一:最完整的写法

2.3.2 方法二:省略“name={ }”

2.3.3 方法三:当只有一个名称时,{} 也可省略

三、扫描路径
问题来了:使用五大注解声明的Bean,一定会生效吗?
答案是不一定。 原因很简单:Bean想要生效,必须被Spring扫描到才行。
下面,我们通过调整项目工程的目录结构,来测试Bean是否生效。

然后运行下面的代码:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
// 获取 Spring 上下文对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
// 从 Spring 上下文中获取对象
User u1 = (User) context.getBean("u1");
// 使用对象
System.out.println(u1);
}
}
运行结果:

报错了: 找不到名称为“u1”的Bean。
为什么找不到?
使用五大注解声明的Bean,要想生效,还需要配置扫描路径,让Spring能够扫描到这些注解。这个任务由@ComponentScan负责。
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
// 获取 Spring 上下文对象
ApplicationContext context =
SpringApplication.run(SpringIocDemoApplication.class, args);
// 从 Spring 上下文中获取对象
User u1 = (User) context.getBean("u1");
// 使用对象
System.out.println(u1);
}
}
{} 中可配置多个包路径,例如:@ComponentScan({"com.example.demo", "com.example.service"})。
注意: 这种做法了解即可,并不推荐在生产环境中使用。
那为什么前面没有显式配置@ComponentScan也能正常运行呢?因为@ComponentScan其实已经包含在启动类上的@SpringBootApplication注解中了。默认情况下,它会扫描启动类所在包及其所有子包。
推荐做法: 将启动类放在我们希望扫描的根包路径下,这样所有定义的Bean都能被自动扫描到。
扫描路径配置总结
- 默认扫描:Spring Boot项目默认扫描启动类所在包及其所有子包。
- 自定义扫描:使用
@ComponentScan注解可以指定要扫描的包路径。 - 多包扫描:通过数组形式指定多个包路径。
- 最佳实践:合理组织项目结构,将需要被扫描的类放在启动类的子包中。
合理配置扫描路径,是确保Spring能够正确发现和管理所有Bean组件的基础,也是IoC容器正常工作的前提条件。
四、DI详解
以上内容主要围绕控制反转(IoC)展开,接下来我们重点讲解依赖注入(DI)。
依赖注入的过程, 简单来说,就是IoC容器在创建Bean时,自动将运行时所需的资源(即其他依赖对象)注入进来。
Spring提供了三种依赖注入方式:
- 属性注入
- 构造方法注入
- Setter注入
4.1 属性注入 @Autowired
代码实现如下:

运行效果:

4.2 构造方法注入
代码实现:

运行效果:

如果只有一个构造方法,@Autowired可以省略。
如果有多个构造方法,默认采用无参构造方法。
可以通过@Autowired来指定使用哪个构造方法。
4.3 setter注入
代码实现:

运行效果:

Setter注入与属性的Setter方法实现类似,区别在于set方法上需要添加@Autowired注解。
4.4 三种注入的优缺点分析
Spring提供了三种依赖注入方式,每种方式都有其适用的场景。了解它们之间的差异,可以帮助我们在实际开发中做出更合适的选择。
1. 属性注入(Field Injection)
优点:
- 简洁直观:代码量最少,直接在字段上添加
@Autowired即可。 - 使用方便:无需编写额外的构造方法或Setter方法。
- 可读性好:依赖关系一目了然,类的依赖项一眼就能看出。
缺点:
- 仅适用于IoC容器:脱离Spring容器后,这种方式无法正常工作。
- 空指针风险:运行时才能发现空指针异常,编译期无法检测。
- 无法注入final字段:不能注入被
final修饰的属性。 - 测试困难:单元测试时需依赖Spring容器,或通过反射设置字段。
- 违反单一职责原则:容易导致类依赖过多,职责不清晰。
2. 构造方法注入(Constructor Injection)
优点:
- 可以注入final字段:支持注入被
final修饰的属性,保证依赖不可变。 - 依赖不可变:注入的对象在构造完成后不会被修改,状态一致性有保障。
- 完全初始化:依赖在使用前一定被完全初始化,因为构造方法在类加载时即执行。
- 通用性好:构造方法是JDK标准特性,不依赖特定框架,切换框架也能使用。
- 便于测试:单元测试中可直接通过构造方法传入模拟对象。
- 强制依赖:明确类的必需依赖,避免依赖缺失。
缺点:
- 代码略显繁琐:依赖较多时,构造方法的参数列表会变长。
- 循环依赖问题:存在循环依赖时,构造方法注入会直接报错。
注意事项:如果类只有一个构造方法,@Autowired可以省略;如果有多个构造方法,则需要通过@Autowired指定使用哪一个。
3. Setter 注入(Setter Injection)
优点:
- 灵活性高:方便在类实例化之后重新配置或注入对象。
- 可选依赖:适合非必需依赖,可以设置默认值或允许为空。
- 便于继承:子类可以重写Setter方法来改变注入行为。
- 解决循环依赖:某些情况下能解决构造方法注入无法处理的循环依赖。
缺点:
- 无法注入final字段:不能注入被
final修饰的属性。 - 依赖可能被改变:Setter方法可能被多次调用,存在被修改的风险。
- 对象状态不稳定:在Setter方法被调用前,依赖可能尚未初始化。
- 时序问题:需要确保在对象使用前,所有必要的Setter方法都已被调用。
三种注入方式对比总结
| 特性 | 属性注入 | 构造方法注入 | Setter 注入 |
|---|---|---|---|
| 代码简洁性 | ★★★★★ | ★★★☆☆ | ★★★★☆ |
| 不可变性 | ★☆☆☆☆ | ★★★★★ | ★★☆☆☆ |
| 测试友好性 | ★★☆☆☆ | ★★★★★ | ★★★★☆ |
| 框架通用性 | ★☆☆☆☆ | ★★★★★ | ★★★★☆ |
| 循环依赖处理 | ★★★★☆ | ★☆☆☆☆ | ★★★★☆ |
| Spring 官方推荐 | Spring 4.x 之前 | Spring 4.x 之后 | Spring 3.x 推荐 |
选择建议
- 强制依赖:推荐使用构造方法注入,确保依赖在对象创建时即正确设置。
- 可选依赖:推荐使用Setter注入,灵活性更高。
- 快速原型:可以使用属性注入快速搭建,但生产环境建议优先选择构造方法注入。
- 不可变对象:必须使用构造方法注入。
- 测试驱动开发:优先考虑构造方法注入,便于编写单元测试。
在实际开发中,Spring官方从4.x版本开始推荐构造方法注入,因为它能保证依赖的不可变性和完全初始化,同时提高代码的可测试性。当然,具体选择哪种方式,还需要结合项目需求和团队规范来决定。
4.5 @Autowired存在的问题
当同一个类型存在多个Bean时,@Autowired会面临歧义问题。

Spring提供了三种解决方案:
- @Primary
- @Qualifier
- @Resource
4.5.1 @Primary
使用@Primary注解:当存在多个相同类型的Bean时,通过@Primary指定一个默认实现。

4.5.2 @Qualifier
使用@Qualifier注解:通过value属性指定要注入的Bean名称。注意,@Qualifier不能单独使用,必须配合@Autowired。

4.5.3 @Resource
使用@Resource注解:按Bean的名称进行注入,通过name属性指定要注入的Bean名称。

五、小结
这几天的作息有些混乱,感觉身体疲惫乏力,可能跟平时缺乏运动有关。原本想报个游泳班,但价格偏高,最终还是决定先暂缓,等以后有机会再考虑。明天吃完火锅后,就要开始认真减肥控糖,一定要坚持瘦下来。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
FileZilla断点续传设置与操作指南
FileZilla支持断点续传,需客户端与服务器均开启REST命令。设置中确保启用断点续传及继续传输选项。中断后自动或手动从断点恢复。注意服务器支持、传输模式匹配及文件完整性校验。
Debian系统C++编译器位置查找方法
在Debian系统中,通过apt安装的C++编译器g++默认位于 usr bin g++,可使用which或whereis命令验证路径。g++属于build-essential软件包,若未安装则需执行sudoaptinstallbuild-essential。该包还包含gcc、make等编译工具链,g++是GNUC++编译器,实际是符号链接指向具体版本,验证
Debian系统安装C++环境的方法
在Debian系统安装C++开发环境:先sudoaptupdate更新包列表,再sudoaptinstallbuild-essential安装编译工具链,或单独安装g++。用g++--version验证。可选安装VSCode、GDB、CMake等工具并配置默认编译器版本。
Debian系统C++开发环境配置指南
在Debian系统中,先执行aptupdate更新软件包列表,再安装build-essential元包即可获得GCC、G++、Make和GDB。通过运行g++--version命令验证编译器安装成功。可选安装VisualStudioCode、CLion等编辑器及CMake构建工具,并编写一个简单的HelloWorld程序,使用g++编译运行以验证环境配置正确
通过cpustat工具查看CPU状态的具体方法与详细步骤
cpustat是sysstat包中的CPU监控工具,可按固定间隔输出带时间戳的CPU使用率统计。安装后运行cpustat即可实时显示各核心信息,常用指标包括%usr、%sys、%iowait、%steal和%idle,用于定位用户态、内核态或I O瓶颈。高级选项-c可显示单核统计,-m可同时查看内存使用,适合脚本采集和性能分析。
- 热门数据榜
相关攻略
2026-07-25 22:29
2026-07-25 22:29
2026-07-25 22:29
2026-07-25 22:29
2026-07-25 22:18
2026-07-25 22:18
2026-07-25 22:18
2026-07-25 22:18
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

