0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Spring开发过程中依赖注入的几个知识点

5jek_harmonyos 来源:掘金 作者:Richard_Yi 2021-08-27 09:18 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

转自丨https://juejin.cn/post/6844904056230690824

本章的内容主要是想探讨我们在进行 Spring 开发过程当中,关于依赖注入的几个知识点。感兴趣的读者可以先看下以下问题:

@Autowired,@Resource,@Inject 三个注解的区别

当你在使用@Autowired时,是否有出现过Field injection is not recommended的警告?你知道这是为什么吗?

Spring 依赖注入有哪几种方式?官方是怎么建议使用的呢?

如果你对上述问题都了解,那我个人觉得你的开发经验应该是不错的。

下面我们就依次对上述问题进行解答,并且总结知识点。

@Autowired,@Resource,@Inject 三个注解的区别

Spring 支持使用@Autowired, @Resource, @Inject 三个注解进行依赖注入。下面来介绍一下这三个注解有什么区别。

@Autowired

@Autowired为Spring 框架提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired。

这里先给出一个示例代码,方便讲解说明:

public interface Svc { void sayHello(); } @Service public class SvcA implements Svc { @Override public void sayHello() { System.out.println(“hello, this is service A”); } } @Service public class SvcB implements Svc { @Override public void sayHello() { System.out.println(“hello, this is service B”); } } @Service public class SvcC implements Svc { @Override public void sayHello() { System.out.println(“hello, this is service C”); } }

测试类:

@SpringBootTest public class SimpleTest { @Autowired // @Qualifier(“svcA”) Svc svc; @Test void rc() { Assertions.assertNotNull(svc); svc.sayHello(); } }

装配顺序:

按照type在上下文中查找匹配的bean

查找type为Svc的bean

如果有多个bean,则按照name进行匹配

如果有@Qualifier注解,则按照@Qualifier指定的name进行匹配

查找name为svcA的bean

如果没有,则按照变量名进行匹配

查找name为svc的bean

匹配不到,则报错。(@Autowired(required=false),如果设置required为false(默认为true),则注入失败时不会抛出异常)

@Inject

在 Spring 的环境下,@Inject和@Autowired 是相同的,因为它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor来处理的。

@Inject是 JSR-330 定义的规范,如果使用这种方式,切换到Guice也是可以的。

❝“Guice 是 google 开源的轻量级 DI 框架”❞

如果硬要说两个的区别,首先@Inject是 Java EE 包里的,在 SE 环境需要单独引入。另一个区别在于@Autowired可以设置required=false而@Inject并没有这个属性。

@Resource

@Resource是 JSR-250 定义的注解。Spring 在 CommonAnnotationBeanPostProcessor实现了对JSR-250的注解的处理,其中就包括@Resource。

@Resource有两个重要的属性:name和type,而Spring 将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。

装配顺序:

如果同时指定了name和type,则从 Spring 上下文中找到唯一匹配的 bean 进行装配,找不到则抛出异常。

如果指定了name,则从上下文中查找名称(id)匹配的 bean 进行装配,找不到则抛出异常。

如果指定了type,则从上下文中找到类型匹配的唯一 bean 进行装配,找不到或是找到多个,都会抛出异常。

如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。

Field injection is not recommended

在使用 IDEA 进行 Spring 开发的时候,当你在字段上面使用@Autowired注解的时候,你会发现 IDEA 会有警告提示:

❝“Field injection is not recommended Inspection info: Spring Team Recommends: “Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”。”

翻译过来就是这个意思:

❝“不建议使用基于 field 的注入方式。Spring 开发团队建议:在你的Spring Bean 永远使用基于constructor 的方式进行依赖注入。对于必须的依赖,永远使用断言来确认。”❞

比如如下代码:

@Service public class HelpService { @Autowired @Qualifier(“svcB”) private Svc svc; public void sayHello() { svc.sayHello(); } } public interface Svc { void sayHello(); } @Service public class SvcB implements Svc { @Override public void sayHello() { System.out.println(“hello, this is service B”); } }

将光标放到@Autowired处,使用Alt + Enter 快捷进行修改之后,代码就会变成基于 Constructor 的注入方式,修改之后:

@Service public class HelpService { private final Svc svc; @Autowired public HelpService(@Qualifier(“svcB”) Svc svc) { // Assert.notNull(svc, “svc must not be null”); this.svc = svc; } public void sayHello() { svc.sayHello(); } }

如果按照 Spring 团队的建议,如果svc是必须的依赖,应该使用Assert.notNull(svc, “svc must not be null”)来确认。

修正这个警告提示固然简单,但是我觉得更重要是去理解为什么 Spring 团队会提出这样的建议?直接使用这种基于 field 的注入方式有什么问题?

首先我们需要知道,Spring 中有这么3种依赖注入的方式:

基于 field 注入(属性注入)

基于 setter 注入

基于 constructor 注入(构造器注入)

1. 基于 field 注入

所谓基于 field 注入,就是在bean的变量上使用注解进行依赖注入。本质上是通过反射的方式直接注入到 field。这是我平常开发中看的最多也是最熟悉的一种方式,同时,也正是 Spring 团队所不推荐的方式。比如:

@Autowired private Svc svc;

2. 基于 setter 方法注入

通过对应变量的setXXX()方法以及在方法上面使用注解,来完成依赖注入。比如:

private Helper helper; @Autowired public void setHelper(Helper helper) { this.helper = helper; }

❝“注:在 Spring 4.3 及以后的版本中,setter 上面的 @Autowired 注解是可以不写的。”

❞3. 基于 constructor 注入

将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。比如:

private final Svc svc; @Autowired public HelpService(@Qualifier(“svcB”) Svc svc) { this.svc = svc; }

❝“在 Spring 4.3 及以后的版本中,如果这个类只有一个构造方法,那么这个构造方法上面也可以不写 @Autowired 注解。”

❞基于 field 注入的好处正如你所见,这种方式非常的简洁,代码看起来很简单,通俗易懂。你的类可以专注于业务而不被依赖注入所污染。你只需要把@Autowired扔到变量之上就好了,不需要特殊的构造器或者set方法,依赖注入容器会提供你所需的依赖。

基于 field 注入的坏处❝“成也萧何败也萧何”❞

基于 field 注入虽然简单,但是却会引发很多的问题。这些问题在我平常开发阅读项目代码的时候就经常遇见。

容易违背了单一职责原则 使用这种基于 field 注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,普通的开发者很可能会无意识地给一个类添加很多的依赖。

但是当使用构造器方式注入,到了某个特定的点,构造器中的参数变得太多以至于很明显地发现 something is wrong。拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则(SRP:Single responsibility principle)。❝“这个问题在我司的项目代码真的很常见。”❞

依赖注入与容器本身耦合依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖。换句话说,这个类应该是一个简单的 POJO(Plain Ordinary Java Object)能够被单独实例化并且你也能为它提供它所需的依赖。这个问题具体可以表现在:

你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化,这更像是集成测试

你的类和依赖容器强耦合,不能在容器外使用

不能使用属性注入的方式构建不可变对象(final 修饰的变量)

Spring 开发团队的建议❝“Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.”❞简单来说,就是

强制依赖就用构造器方式

可选、可变的依赖就用 setter 注入当然你可以在同一个类中使用这两种方法。构造器注入更适合强制性的注入旨在不变性,Setter 注入更适合可变性的注入。

让我们看看 Spring 这样推荐的理由,首先是基于构造方法注入,❝“The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.”❞Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了。

与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任。

而对于基于 setter 的注入,他们是这么说的:❝“Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.”❞

基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入。

小结

以上就是本文的所有内容,希望阅读本文之后能让你对 Spring 的依赖注入有更深的理解。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 开发
    +关注

    关注

    0

    文章

    380

    浏览量

    42232
  • spring
    +关注

    关注

    0

    文章

    341

    浏览量

    16046
  • 智能构造器
    +关注

    关注

    0

    文章

    2

    浏览量

    5667

原文标题:Spring官方为什么建议构造器注入?

文章出处:【微信号:harmonyos_developer,微信公众号:harmonyos_developer】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    示波器小知识点

    示波器的系统带宽:是指一个逐渐升高频率的正弦输入信号显示幅度下降3dB时的频率。测试信号一般是方波,而方波信号由基波及奇次谐波组成。如果用示波器去测一个方波,需要注意示波器应从频率上能够测量到它
    的头像 发表于 04-16 11:04 58次阅读
    示波器小<b class='flag-5'>知识点</b>

    深入解析RK平台编译核心:build.sh的知识点、调试技巧与开发价值

    在瑞芯微(RK)Linux SDK 开发,build.sh是整个编译构建系统的“入口中枢”—— 它统一管理环境配置、命令解析、模块构建与日志输出,几乎所有芯片(如 RK3588、RV1126)的固件编译、内核构建、根文件系统定制都依赖
    的头像 发表于 02-03 16:02 3219次阅读
    深入解析RK平台编译核心:build.sh的<b class='flag-5'>知识点</b>、调试技巧与<b class='flag-5'>开发</b>价值

    开发过程中如何利用CW32L083系列微控制器的官方固件库进行程序编写和调试?

    开发过程中,如何利用CW32L083系列微控制器的官方固件库进行程序编写和调试?
    发表于 12-15 07:23

    开发指南】全志系列核心板开发过程中的常见问题及排查策略

    在长期提供技术支持服务的过程中,飞凌嵌入式总结了用户开发全志系列产品时常见的问题及排查方法。本文中,小编将为大家梳理这些经验,助力开发者快速定位问题,提升开发效率。
    的头像 发表于 10-15 08:04 7029次阅读
    【<b class='flag-5'>开发</b>指南】全志系列核心板<b class='flag-5'>开发过程中</b>的常见问题及排查策略

    芯片研发过程中的两种流片方式

    芯片在研发过程中一般包含4个阶段:芯片设计、生产样片、测试验证和大规模量产。在完成芯片设计后,工程师们需要先拿到一些芯片样片,用它们进行测试和验证,来判断新研发的芯片在功能和性能上是否符合设计要求
    的头像 发表于 09-09 15:04 2487次阅读
    芯片研<b class='flag-5'>发过程中</b>的两种流片方式

    如何保障远程运维过程中的数据安全和隐私?

    LZ-DZ100背面 在分布式光伏集群的远程运维,数据安全和隐私保护面临多重风险,包括 传输过程中的窃听 / 篡改、未授权访问控制指令、设备固件被恶意植入、敏感数据(如站点位置、运行参数)泄露 等
    的头像 发表于 08-22 10:26 1179次阅读
    如何保障远程运维<b class='flag-5'>过程中</b>的数据安全和隐私?

    使用非隔离电源的触控项目,遇到CS注入电流10v 动态CS,在触控扫描F上触摸按不动,怎么解决?

    你好,在使用非隔离电源开发 触控项目,在进行10V cs注入电流测试时,发现在触控扫描频的倍频上触摸无法按动的情况,例如触控扫描频2M,即在CS
    发表于 08-08 07:18

    功能安全开发的“降本利器”:高效平台化工具链实战

    随着智能驾驶技术的快速发展,汽车电子电气(E/E)系统的复杂度显著提升,功能安全已成为其开发过程中不可或缺的关键要素。依据ISO26262标准要求,功能安全开发活动通常数量众多且关联性强。基于以往
    的头像 发表于 07-10 14:27 734次阅读
    功能安全<b class='flag-5'>开发</b>的“降本利器”:高效平台化工具链实战

    半导体硅片生产过程中的常用掺杂技术

    在半导体硅片生产过程中,精确调控材料的电阻率是实现器件功能的关键,而原位掺杂、扩散和离子注入正是达成这一目标的核心技术手段。下面将从专业视角详细解析这三种技术的工艺过程与本质区别。
    的头像 发表于 07-02 10:17 2765次阅读
    半导体硅片生产<b class='flag-5'>过程中</b>的常用掺杂技术

    【「Yocto项目实战教程:高效定制嵌入式Linux系统」阅读体验】01初读体验

    书的心得体会,获取阅读重点等信息。 推荐序知识点 从推荐序,可以获得以下知识点: 这本书的学习过程中可以实操,没有硬件条件的可以使用qemu模拟,有硬件条件的可以使用树莓派5或者im
    发表于 06-30 21:49

    HarmonyOS实战: 城市选择功能的快速实现

    最近在日常开发过程中,需要实现城市选择功能,同时支持模糊搜索。看似简单的功能动手实现起来却有很多难点。本篇文章详细记录开发过程中遇到的问题和对应的解决方法,希望能够帮助你,建议赞收藏!
    的头像 发表于 06-24 17:07 570次阅读

    前端开发依赖包有问题怎么办

    在前端开发,如果你发现某个依赖包存在问题,可以考虑以下步骤来解决: 一、简单方案 1. 检查问题来源 : 确认问题是否由依赖包引起,而不是你的代码或其他配置问题。 查看错误信息、文档
    的头像 发表于 06-10 11:31 590次阅读

    使用CY7C65213开发过程中,应该用哪个interface进行uart通信?

    在使用CY7C65213开发过程中,我想用CyUartRead读数据,但是好像没有接口的deviceType是CY_TYPE_UART,想请问我应该用哪个interface进行uart通信? 是否有相关指导文件,或描述符指导?
    发表于 06-03 07:04

    明远智睿SSD2351开发板:语音机器人领域的变革力量

    源的开发资料为开发者提供了深入研究和定制语音机器人功能的基础,开发者可以根据不同的应用需求,对语音识别算法、语音合成引擎等进行优化和改进。一对一的技术支持则能及时解决开发过程中遇到的难
    发表于 05-28 11:36

    电机选型计算公式与知识点汇总

    纯分享帖,需要者可点击附件获取完整资料~~~*附件:电机选型计算公式与知识点汇总.pdf 【免责声明】内容转自今日电机,因转载众多,无法确认真正原始作者,故仅标明转载来源。版权归原出处所有,纯分享帖,侵权请联系删除内容以保证您的权益。
    发表于 04-29 16:10