当前位置: 首页 > news >正文

深入解析Spring依赖注入 DI 的三种方式

前言

在之前的一篇文章Spring Bean的配置方式中,我们学习到了如何配置Bean并将其放到IOC容器里的几种注解,这篇文章我们主要讲注入的相关注解,这些注解负责从容器里取出Bean并注入到需要的地方

先配置Bean(@Service、@Repository、@Component...),再注入Bean,二者协同完成Spring的依赖注入的完整流程,缺一不可

这篇主要讲解注入相关的几个注解:@Autowired、@Inject、@Resource

一、@Autowired

1.定义

@Autowired是Spring框架自带的依赖注入注解,其核心作用是:自动装配符合条件的Bean到当前类的属性、方法或构造器中,无需手动创建Bean实例,由Spring容器统一管理和注入。

@Autowired可用于字段、构造器、setter方法、方法参数,四种场景各有适用场景,其中构造器注入是Spring官方推荐的方式、而参数注入适用于局部方法需要依赖Bean的场景,灵活性更高

2.核心原理

@Autowired的注入逻辑遵循“先byType,后byName”的规则,具体流程如下:

  1. Spring容器启动时,会扫描所有带有@Autowired注解的字段、方法或构造器;
  2. 首先按照“类型匹配”(byType)查找容器中是否存在唯一的、与目标类型一致(或兼容,如子类、实现类)的Bean;
  3. 若找到唯一匹配的Bean,直接注入;
  4. 若找到多个类型匹配的Bean,会切换为“名称匹配”(byName),即根据目标字段名、方法参数名,匹配Bean的id(默认是类名首字母小写);若多个Bean名称相同,会抛出NoUniqueBeanDefinitionException异常,有两种解决方案:@Primary、@Qualifier
  5. 若既没有唯一类型匹配,也没有名称匹配的Bean,且@Autowired未设置required=false,则抛出NoSuchBeanDefinitionException异常;若设置required=false,则注入null(不推荐,易引发空指针)。

3.字段注入

直接在类的成员变量上添加@Autowired注解,无需setter方法,Spring会自动将匹配的Bean注入到该字段中。

注意:

  • 优点:字段注入无需手动编写setter方法,代码简洁;

  • 缺点:可读性稍差,且不利于单元测试(无法通过setter方法手动注入模拟Bean);

  • 不能用于静态字段(如private static UserDao userDao; 注入无效,会为null)。

// 服务层Bean @Service public class UserService { // 字段注入:自动注入UserDao类型的Bean @Autowired private UserDao userDao; // 业务方法,直接使用注入的userDao public User getUserById(Long id) { return userDao.selectById(id); } } // 持久层Bean(@Repository会自动注册到Spring容器) @Repository public class UserDaoImpl implements UserDao { @Override public User selectById(Long id) { // 模拟数据库查询 return new User(id, "张三", 20); } }

4.构造器注入(官方推荐)

在类的构造器上添加@Autowired注解,Spring会在实例化该Bean时,通过构造器将匹配的Bean注入。

注意:

  • 如果bean只有一个有参构造函数(这个有参构造函数会覆盖掉默认的无参构造函数),可以省略@Autowired,因为会自动注入构造函数的参数;
  • 如果有多个构造函数并且没有无参构造函数时,不添加@Autowired,就会报错,因此需要添加这个注解来指定默认

优势:

  • 构造器注入可以保证Bean实例化时,所有依赖都已注入完成,避免了字段注入可能出现的空指针问题;
  • 依赖关系明确,通过构造器参数就能清晰看到该Bean需要哪些依赖;
@Service public class UserService { private final UserDao userDao; private final RoleDao roleDao; // 构造器注入:注入多个依赖 @Autowired // 当只有一个有参构造器时,可省略此注解 public UserService(UserDao userDao, RoleDao roleDao) { this.userDao = userDao; this.roleDao = roleDao; } // 业务方法 public User getUserWithRole(Long userId) { User user = userDao.selectById(userId); List<Role> roles = roleDao.selectByUserId(userId); user.setRoles(roles); return user; } }

5.setter方法注入

在setter方法上添加@Autowired注解,Spring会调用该setter方法,将匹配的Bean注入到字段中。适用于依赖可选(可通过required=false设置)的场景。

注意:

  • required=false表示该依赖不是必须的,若Spring容器中没有匹配的Bean,不会抛出异常,而是注入null,使用时需做非空判断。
@Service public class UserService { private UserDao userDao; // setter方法注入 @Autowired(required = false) // 依赖可选,没有匹配Bean时注入null public void setUserDao(UserDao userDao) { this.userDao = userDao; } // 业务方法,需先判断userDao是否为null public User getUserById(Long id) { if (userDao == null) { throw new RuntimeException("UserDao未注入"); } return userDao.selectById(id); } }

6.方法参数注入

在普通方法(非setter、非构造器)的参数上添加@Autowired注解,Spring会自动将匹配的Bean注入到该参数中,适用于局部方法需要临时依赖某个Bean的场景,无需将依赖注入为类的全局字段,减少不必要的全局依赖。

注意:

  • 参数注入的Bean仅在当前方法内有效,不属于类的全局属性,避免全局字段冗余;
  • 可同时为多个方法参数添加@Autowired,Spring会分别匹配注入对应的Bean;

7.@Qualifier & @Primary

在讲核心原理的第四步时,提到了当Spring容器中存在多个相同类型的Bean时,@Autowired的“先byType,后byName”规则可能无法满足需求(比如字段名与Bean的id不匹配)

此时可以通过@Qualifier(精准指定)@Primary(默认首选)解决冲突

(1)@Qualifier:精准指定

@Qualifier必须与@Autowired配合使用,通过value属性指定要注入的Bean的id,精准匹配,彻底解决多Bean冲突问题。

// 两个相同类型的Bean(UserDao的两个实现类) @Repository("userDaoMysql") // Bean的id为userDaoMysql public class UserDaoMysqlImpl implements UserDao { @Override public User selectById(Long id) { return new User(id, "张三(Mysql)", 20); } } @Repository("userDaoOracle") // Bean的id为userDaoOracle public class UserDaoOracleImpl implements UserDao { @Override public User selectById(Long id) { return new User(id, "张三(Oracle)", 20); } } // 服务层注入,指定注入id为userDaoMysql的Bean @Service public class UserService { // @Qualifier配合@Autowired,指定Bean的id @Autowired @Qualifier("userDaoMysql") private UserDao userDao; // 业务方法 public User getUserById(Long id) { return userDao.selectById(id); // 使用的是Mysql实现 } }

(2)@Primary:默认首选

@Primary添加在Bean的实现类上,标识该Bean为“默认首选Bean”,当存在多个同类型Bean时,Spring会自动选择带有@Primary注解的Bean注入,无需在注入端额外配置。

// 两个相同类型的Bean,其中一个添加@Primary @Repository @Primary // 标记为默认首选Bean public class UserDaoMysqlImpl implements UserDao { @Override public User selectById(Long id) { return new User(id, "张三(Mysql)", 20); } } @Repository public class UserDaoOracleImpl implements UserDao { @Override public User selectById(Long id) { return new User(id, "张三(Oracle)", 20); } } // 服务层注入,无需指定@Qualifier,自动注入@Primary标记的Bean @Service public class UserService { @Autowired private UserDao userDao; // 自动注入UserDaoMysqlImpl(@Primary标记) public User getUserById(Long id) { return userDao.selectById(id); // 输出:张三(Mysql) } }

二、@Inject

1.定义

@Inject是JSR-330(Java Dependency Injection)标准注解,位于javax.inject包下,不属于Spring框架,是一种通用的依赖注入注解,可用于替代@Autowired,适用于需要脱离Spring框架、追求标准化的场景。

2.与@Autowired主要区别

无赖Spring依赖,没有属性(在@Autowired中有required属性),因此依赖必须存在,不存在就抛出异常

3.使用方式

有字段注入、构造器注入,示例如下:

// 需导入依赖(Maven) <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1.0</version> </dependency> @Service public class UserService { // 字段注入 @Inject private UserDao userDao; // 构造器注入(无需额外配置,与@Autowired一致) @Inject public UserService(RoleDao roleDao) { this.roleDao = roleDao; } }

三、@Resource

1.定义

@Resource是JSR-250(Java Platform Enterprise Edition)标准注解,位于javax.annotation包下,也是一种通用的依赖注入注解

2.与@Autowired主要区别

与@Autowired的核心区别是:默认按名称匹配(byName),而非按类型匹配

有name、type两个属性,可以单独或组合使用

3.使用方式

有字段注入、setter方法注入,不支持构造器注入、方法参数注入,示例如下:

@Service public class UserService { // 1. 只指定name,按Bean的id匹配(name属性值=Bean的id) @Resource(name = "userDaoMysql") private UserDao userDao; // 2. 不指定name,默认按字段名匹配(字段名=Bean的id) @Resource private RoleDao roleDao; // 匹配id为roleDao的Bean // 3. 指定type,按类型匹配(与@Autowired的byType一致) @Resource(type = UserDao.class) private UserDao userDao2; // 4. 同时指定name和type,必须同时匹配才会注入 @Resource(name = "userDaoOracle", type = UserDao.class) private UserDao userDao3; }
http://www.jsqmd.com/news/792219/

相关文章:

  • 【大模型版本管理黄金法则】:奇点智能大会首发的7大避坑指南与企业落地 checklist
  • [深度学习-实战篇]情感分析之TextCNN:从理论到工业级部署,含完整项目代码
  • 2026年短视频去水印工具推荐排行:哪款去水印工具好用?怎么去掉视频水印?
  • 20260510 4
  • DeepSeek拟融500亿,低价开源下营收堪忧,爆款产品能否撑起515亿美元估值?
  • 别再为通讯发愁!手把手教你用S7A驱动搞定IFIX与西门子PLC以太网连接
  • 图解PyTorch gather函数:从困惑到精通,一个例子讲清张量收集操作
  • 跨站请求伪造(CSRF)
  • AI技术大会摄影服务落地实录(SITS2026独家技术白皮书首发)
  • 英伟达巨额投资,四大云巨头财报亮眼,半导体产业扩张背后隐忧浮现
  • JiYuTrainer深度解析:3大核心技术实现极域电子教室破解与系统控制实战
  • day05补发
  • 2026年4月评价高的高密度硅酸钙板品牌推荐,玻璃热弯模具/汽车后视镜热弯模具,高密度硅酸钙板厂家怎么选择 - 品牌推荐师
  • 2026年4月行业内评价好的不锈钢板实力厂家口碑推荐,不锈钢装饰管/不锈钢折弯/不锈钢角钢,不锈钢板公司哪个好 - 品牌推荐师
  • 洛谷 P1333:瑞瑞的木棍 ← 欧拉回路 + 并查集
  • 掌握 ruby-build 环境变量配置:7 个技巧让 Ruby 安装效率翻倍
  • apio2026游记
  • 团队项目第二次作业
  • sparksql读取mysql表处理etl数据加工过程在把结果反插入库
  • 跨境电商物流解决方案-恒盛通国际快递服务 - 恒盛通物流
  • day05补发补充
  • 2026 年豆包开启付费订阅,中国 AI 大模型商业化迎来大考!
  • 时序数据库详解
  • 软工5月10号
  • Display Driver Uninstaller (DDU):彻底清理显卡驱动的终极解决方案
  • STM32 SDIO+PCM5102成功播放《义妹》
  • day04补发
  • 深入了解Python并发编程
  • 如何通过Noto Emoji实现跨平台表情符号统一:技术原理与应用实践
  • Qt/C++实战:手把手教你用QCustomPlot实现动态刷新热力图(模拟实时数据)