《Spring Bean&DI 通关笔记:从定义到注入的全场景避坑指南》

一、为什么需要多类注解?

这些注解是为了匹配应用分层 ,让开发者通过注解直接识别类的用途(类似车牌标识车辆归属地),提升代码的可读性与分层清晰度。

注解 对应分层 作用描述
@Controller 控制层 接收、处理请求并响应
@Service 业务逻辑层 处理具体业务逻辑
@Repository 数据访问层 负责数据库等数据操作
@Configuration 配置层 处理项目配置信息
1.1类注解的底层关系

@Controller@Service@Repository等注解,本质是**@Component** 的衍生注解 (相当于@Component的 "子类"):

  • 从源码可看到,这些注解都包含@Component标识,因此它们的核心功能(将类注册为 Spring Bean)与@Component一致;
  • 区别仅在于语义化:不同注解对应不同分层场景,让代码的用途更直观(类似 "水杯""刷牙杯" 都是杯子,但用途更明确)。
1.2实际开发的选择原则

在对应分层中优先使用语义化注解(而非通用的@Component):

  • 业务逻辑层用@Service而非@Component
  • 控制层用@Controller而非@Component
  • 这样能让代码的分层意图更清晰,便于团队协作与维护。

程序的应⽤分层,调⽤流程如下:

二、@Bean的适用场景

类注解(如@Service)无法满足以下场景时,需使用方法注解@Bean

  1. 管理外部依赖的类:无法给外部包中的类添加类注解。
  2. 同一类需要多个对象 :比如项目需要多个数据源(同一DataSource类需创建多个不同配置的对象)。

2.1@Bean的使用陷阱:必须配合类注解

单独使用@Bean无法将对象存入 Spring 容器,需满足:

  • @Bean标记的方法所在类,必须添加类注解 (如@Component@Configuration),让 Spring 识别并扫描该类,进而执行@Bean方法创建对象。

第三方类(比如RestTemplate)的代码你无法修改,所以:

  1. 没法给它加@Service等类注解,自然不会被 Spring 自动管理;
  2. 你需要自己写一个方法 ,在方法里手动创建第三方类的对象(new RestTemplate());
  3. 给这个方法加@Bean注解,同时给方法所在类加@Configuration(或@Component)------ 这样 Spring 会扫描到这个类,执行@Bean方法,把第三方对象存为 Spring 的 Bean;
  4. 之后就能用@Autowired正常注入这个第三方对象(因为它已经被 Spring 管理了)。

核心总结:@Bean的作用,就是把 "你手动创建的对象(不管是自己写的还是第三方的)",纳入 Spring 的管理范围 ------ 本质是给第三方类 "补加" 了一个 "存对象" 的能力 ,补上之后,就能和自己的类一样用@Autowired了。

用第三方类,必须:

  1. 手写一个自己的配置类 (加@Configuration/@Component);
  2. 在配置类里写@Bean方法,手动实例化第三方类的对象(相当于替 Spring 完成 "创建第三方对象并存入容器" 的工作);
  3. 后续想用第三方类的功能时:
    • 先通过@Autowired注入这个 "被 Spring 管理的第三方对象"(
    • 再通过 "注入的对象。方法 ()" 调用功能.

2.2定义多个对象

针对同一个类(如User),通过@Bean可以定义多个不同配置的对象(比如多数据源场景),每个@Bean对应的方法会生成一个独立的 Bean 实例。

注意: 在一个类里定义多个同类对象」≠「必须用 @Bean」,@Bean只是Spring 容器管理多实例的一种方式,而非唯一方式;且「注入」是「使用对象」的动作,和「定义对象」是两回事。

2.2.1不同场景下定义多实例:

场景 1:第三方类(必须用 @Bean)

RestTemplate(第三方类,无法加 @Component)为例,定义 2 个差异化配置的实例:

场景 2:自定义类(3 种方式)

User(自定义类)为例,演示 3 种多实例定义方式:

方式 1:@Bean(配置类,支持差异化配置)

方式 2:@Component + @Scope(原型模式,配置逻辑一致)
方式 3:手动 new(非 Spring 管理)

三、使用多实例(注入时指定名称)

以场景 2 的方式 1 为例,注入并使用user1user2

关键区分:
维度 场景 1:第三方类(如 RestTemplate) 场景 2:自定义类 - 方式 1(@Bean) 场景 2:自定义类 - 方式 2(@Component+@Scope) 场景 2:自定义类 - 方式 3(手动 new)
是否需 Spring 管理 是(必须)
定义方式 配置类 +@Bean 配置类 +@Bean 类上加 @Component+@Scope ("prototype") 直接 new 对象
是否支持差异化配置 支持(每个 @Bean 独立配置) 支持(每个 @Bean 独立配置) 不支持(所有实例属性 / 逻辑一致) 支持(手动设置不同属性)
Bean 名称 @Bean ("名称") 指定(默认方法名) @Bean ("名称") 指定 默认类名首字母小写(如 user) 无 Bean 名称(非 Spring 管理)
注入使用方式 @Autowired+@Qualifier ("名称") @Autowired+@Qualifier ("名称") @Autowired(每次注入新实例,但配置一致) 直接调用 new 出的对象(无注入)
核心优势 能管理第三方类的多实例 灵活控制自定义类多实例配置 无需配置类,代码简洁 脱离 Spring,无容器依赖
核心局限 需写配置类 需写配置类 实例配置无法差异化 无法使用 Spring 的依赖注入 / 生命周期
典型使用场景 多数据源、多策略 RestTemplate 自定义类多实例差异化配置 简单多实例(无需差异化) 临时使用、非核心业务对象

结论:

关键结论

  1. 第三方类必选 @Bean:因为无法修改源码加 @Component,只能通过 @Bean 让 Spring 管理多实例;
  2. 自定义类优先选 @Bean:如果需要差异化配置(如 user1/zhangsan、user2/lisi),@Bean 是最优解;
  3. @Component+@Scope 仅适用于简单场景:仅需要 "多实例" 但不需要 "差异化配置" 时用;
  4. 手动 new 仅适用于非 Spring 管理场景:不需要依赖注入、生命周期管理时用。

四、扫描路径

Bean 通过@Component/@Bean等注解声明后,必须被 Spring 扫描到才能生效,否则 Spring 不会创建该 Bean,导致使用时找不到。

  1. 默认扫描范围@SpringBootApplication内置了@ComponentScan,默认扫描启动类所在的包及其子包;若 Bean 不在这个范围内,会因未被扫描而不生效
  2. 手动补全扫描路径 :若 Bean 不在默认范围,可通过@ComponentScan({"包路径1", "包路径2"})手动指定扫描包,但不推荐(易出错)。
  3. 推荐做法 :将启动类放在项目的根包下(如com.example.demo),使所有业务包(component/configuration/controller等)都处于默认扫描范围内,无需额外配置。

五、DI详解

依赖注入(DI)是 IOC 容器在创建 Bean 时,自动为其提供所依赖的对象(资源)的过程;@Autowired是实现依赖注入的常用注解之一。

关于依赖注⼊,Spring也给我们提供了三种⽅式:

  1. 属性注⼊(FieldInjection)
  2. 构造⽅法注⼊(ConstructorInjection)
  3. Setter注⼊(SetterInjection)
5.1属性注⼊

属性注⼊是使⽤@Autowired 实现的,将Service类注⼊到Controller类中.

Service类的实现代码如下:

Controller类的实现代码如下:

获取Controller中的sayHi⽅法:

结果:

5.2构造⽅法注⼊ 构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所⽰:

注意事项:如果类只有⼀个构造⽅法,那么@Autowired注解可以省略;如果类中有多个构造⽅法, 那么需要添加上@Autowired来明确指定到底使⽤哪个构造⽅法。

5.3Setter注⼊

Setter注⼊和属性的Setter⽅法实现类似,只不过在设置set⽅法的时候需要加上@Autowired注 解,如下代码所⽰:

三种注入方式的优缺点对比
注入方式 优点 缺点 推荐版本
属性注入 代码简洁、使用方便 1. 仅适用于 IOC 容器,非 IOC 环境下会空指针;2. 无法注入 final 修饰的属性 -
构造方法注入 1. 可注入 final 属性;2. 依赖对象初始化时就确定,不会被修改;3. 通用性强(不依赖 IOC 容器) 注入多个对象时,构造方法参数会较多,代码繁琐 Spring 4.X+
Setter 注入 可在实例创建后,重新配置 / 注入依赖对象 1. 无法注入 final 属性;2. 依赖对象可能被多次修改(Setter 可重复调用) Spring 3.X(旧)
  • 构造方法注入是Spring 推荐的方式,更安全、通用性更强;
  • 属性注入仅适用于简单场景,需注意其局限性;
  • Setter 注入仅在需要 "动态修改依赖" 的场景下使用。
5.4@Autowired存在问题

当同⼀类型存在多个bean时,使⽤@Autowired会存在问题

5.4.1核心问题:

当同一个类(如User)被定义为多个 Bean 实例(如user1user2)时,仅用@Autowired注入会触发 **"找到多个匹配 Bean" 的异常 **(因为 Spring 无法确定要注入哪个实例)。

5.4.2三种解决方法
1. @Primary:指定默认 Bean

在其中一个@Bean方法上添加@Primary,声明该 Bean 为 "默认实例",当@Autowired未指定具体 Bean 时,会自动注入这个默认实例。

2. @Qualifier:指定 Bean 名称

@Autowired配合使用,通过value属性指定要注入的 Bean 名称,精准匹配实例。

3. @Resource:按名称注入

通过name属性指定 Bean 名称(是 JDK 提供的注解,非 Spring 专属),直接按名称匹配实例。

@Autowired@Resource的区别

维度 @Autowired @Resource
所属框架 Spring 提供 JDK 提供
注入逻辑 默认按类型注入 默认按名称注入
支持的属性 仅配合@Qualifier指定名称 直接通过name属性指定名称

当同一类型存在多个 Bean 时,需通过@Primary(指定默认)、@Qualifier(配合@Autowired指定名称)或@Resource(按名称注入)来明确要注入的实例,避免冲突。

相关推荐
Blossom.11812 小时前
边缘智能新篇章:YOLOv8在树莓派5上的INT8量化部署全攻略
人工智能·python·深度学习·学习·yolo·react.js·transformer
郝学胜-神的一滴12 小时前
Linux多线程编程:深入解析pthread_detach函数
linux·服务器·开发语言·c++·程序人生
2501_9307077812 小时前
使用C#代码重新排列 PDF 页面
开发语言·pdf·c#
小Mie不吃饭12 小时前
Spring boot + mybatis-plus + Redis 实现数据多级缓存(模拟生产环境)
java·spring boot·redis·mysql·缓存
海盗猫鸥12 小时前
「C++」多态
开发语言·c++
不思念一个荒废的名字12 小时前
【黑马JavaWeb+AI知识梳理】Web后端开发08 - 总结
java·后端
wdfk_prog12 小时前
[Linux]学习笔记系列 -- [fs]locks
linux·笔记·学习
黎雁·泠崖12 小时前
C 语言预处理核心(上):预定义符号 + #define 常量与宏全解析
c语言·开发语言
heartbeat..12 小时前
Java IO 流完整解析:原理、分类、使用规范与最佳实践
java·开发语言·io·文件
csbysj202012 小时前
MongoDB $type 操作符
开发语言