一、Bean的存储
在上一节的案列中,如果需要把某个对象交给IoC容器管理,需要在类上添加一个注解:@Component,而Spring框架为了更好的服务web应用程序,提供了更丰富的注解
类注解:@Controller、@Service、@Repository、@Component、@Configuration
方法注解:@Bean
1.1 @Controller(控制器存储)
使用@Controller存储bean

从spring容器中获取对象,

上述代码是根据类型来查找的对象,但如果一个Spring容器中,同一个类型存在多个Bean的话,怎么获取呢,(bean的命名上一小节有分享过,这里就不赘述了)
1.根据bean名称获取bean
Object getBean(String var1)throws BeansException;
2.根据bean名称和类型获取bean
<T> T getBean(String var1,Class<T> var2)throws BeansException;
3.根据类型获取bean
<T> T getBean(Class<T> var1)throws BeansException;
4. 按bean名称和构造函数动态创建bean,只适用于具有原型(prototype)作用域的bean
Object getBean(String var1,Class<T> var2)throws BeansException;
5.按bean类型和构造函数动态创建bean,只适用于具有原型(prototype)作用域的bean
<T> T getBean(String var1,Class<T> var2)throws BeansException;
因为实际应用中1 2 3频率最高,就只展示这三种,后续几个注解也是

查看是否是同一个对象

可以看出,地址一致,获取的bean的对象是同一个
1.2 @Service(服务存储)
使用@Service存储bean

读取bean

1.3 @Repository(仓库存储)
使用@Repository存储bean

读取bean

1.4 @Component(组件存储)
使用@Component存储bean

读取bean

1.5 @Configuration(配置存储)
使用@Cofiguration存储bean

读取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属于BeanFactory的子类,它除了基础BeanFactory的所有功能之外,还拥有独特的特性,添加了国际化支持、资源访问支持、以及事件传播等方面的支持
- 从性能方面来说: ApplicationContext是一次性加载并初始化所有的Bean对象,而BeanFactory是需要哪个才去加载那个,因此更加轻量
二、方法注解@Bean
上面我们介绍了五大类注解,但存在两个问题:
1,使用外部包里的类,没办法添加类注解
2,一个类,需要多个对象,如多个数据源
基于这两个问题,我们就需要使用方法注解@Bean,@Bean注解也需搭配五大注解使用
2.1 搭配类注解的使用


在Spring框架的设计中,方法注解@Bean要配合类注解才能将对象正常存储到spring容器中
如果注掉@Component

2.2 定义多个对象

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

2.3.2 方法二:省略"name={ }"

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

三、扫描路径
Q:使用前面学习的五大注解声明的 Bean,一定会生效吗?
**A:不一定生效。**原因在于:Bean 想要生效,还需要被 Spring 扫描到。
下面我们通过修改项目工程的目录结构,来测试 Bean 对象是否生效:

再运行代码:
java
@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 对象呢?
使用五大注解声明的 Bean,要想生效,还需要配置扫描路径,让 Spring 扫描到这些注解。也就是通过 @ComponentScan 来配置扫描路径。
java
@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 中了。默认扫描的范围是 Spring Boot 启动类所在包及其子包。
在配置类上添加 @ComponentScan 注解,该注解默认会扫描该类所在的包下所有的配置类。
**推荐做法:**把启动类放在我们希望扫描的包的路径下,这样我们定义的 Bean 就都可以被扫描到。
扫描路径配置总结
- **默认扫描:**Spring Boot 项目默认扫描启动类所在包及其所有子包
- 自定义扫描: 使用
@ComponentScan注解可以指定要扫描的包路径 - **多包扫描:**可以通过数组形式指定多个包路径
- **最佳实践:**合理组织项目结构,将需要被扫描的类放在启动类的子包中
通过合理配置扫描路径,可以确保 Spring 能够正确发现和管理所有的 Bean 组件,这是 Spring 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 容器,这种方式无法工作
- **空指针风险:**只有在使用的时候才会出现 NPE(空指针异常),编译期无法发现
- 无法注入 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注解:指定当前要注人的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。@Qualifier注解不能单独使用,必须配合@Autowired使用

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

五、小结
感觉这几天有点昼夜颠倒了,好困好困好累好累,怀疑是不是日常没什么运动量导致身体虚虚的。想报个游泳班,但是都好贵啊,最后还是决定不报了,等后面有机会了在报吧。明天吃完火锅后,就开始减肥控糖,一定要瘦瘦瘦