Spring官方为什么建议构造器注入?

沙海 2021年7月27日04:08:00Java评论39字数 6683阅读22分16秒阅读模式
摘要

智能摘要

智能摘要文章源自JAVA秀-https://www.javaxiu.com/39207.html

下面我们就依次对上述问题进行解答,并且总结知识点。如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。扔到变量之上就好了,不需要特殊的构造器或者set方法,依赖注入容器会提供你所需的依赖。注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,普通的开发者很可能会无意识地给一个类添加很多的依赖。setter的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。文章源自JAVA秀-https://www.javaxiu.com/39207.html

原文约 3091 | 图片 5 | 建议阅读 7 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/39207.html

Spring官方为什么建议构造器注入?

戳一戳→ 程序员的成长之路 文章源自JAVA秀-https://www.javaxiu.com/39207.html

Spring官方为什么建议构造器注入?文章源自JAVA秀-https://www.javaxiu.com/39207.html

程序员的成长之路文章源自JAVA秀-https://www.javaxiu.com/39207.html

互联网/程序员/技术/资料共享 文章源自JAVA秀-https://www.javaxiu.com/39207.html

关注文章源自JAVA秀-https://www.javaxiu.com/39207.html

阅读本文大概需要 7 分钟。文章源自JAVA秀-https://www.javaxiu.com/39207.html

来自:https://juejin.cn/post/6844904056230690824文章源自JAVA秀-https://www.javaxiu.com/39207.html

前言

本章的内容主要是想探讨我们在进行 Spring 开发过程当中,关于依赖注入的几个知识点。感兴趣的读者可以先看下以下问题:文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • @Autowired@Resource@Inject 三个注解的区别文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • 当你在使用@Autowired时,是否有出现过Field injection is not recommended的警告?你知道这是为什么吗?文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • Spring 依赖注入有哪几种方式?官方是怎么建议使用的呢?文章源自JAVA秀-https://www.javaxiu.com/39207.html

如果你对上述问题都了解,那我个人觉得你的开发经验应该是不错的?。文章源自JAVA秀-https://www.javaxiu.com/39207.html

下面我们就依次对上述问题进行解答,并且总结知识点。文章源自JAVA秀-https://www.javaxiu.com/39207.html

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

Spring 支持使用@Autowired, @Resource,  @Inject 三个注解进行依赖注入。下面来介绍一下这三个注解有什么区别。文章源自JAVA秀-https://www.javaxiu.com/39207.html

@Autowired

@Autowired为Spring 框架提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired文章源自JAVA秀-https://www.javaxiu.com/39207.html

这里先给出一个示例代码,方便讲解说明:文章源自JAVA秀-https://www.javaxiu.com/39207.html

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

测试类:文章源自JAVA秀-https://www.javaxiu.com/39207.html

@SpringBootTestpublic class SimpleTest {    @Autowired    // @Qualifier("svcA")    Svc svc;    @Test    void rc() {        Assertions.assertNotNull(svc);        svc.sayHello();    }}

装配顺序:文章源自JAVA秀-https://www.javaxiu.com/39207.html

  1. 按照type在上下文中查找匹配的bean文章源自JAVA秀-https://www.javaxiu.com/39207.html

    查找type为Svc的bean
  2. 如果有多个bean,则按照name进行匹配文章源自JAVA秀-https://www.javaxiu.com/39207.html

    1. 如果有@Qualifier注解,则按照@Qualifier指定的name进行匹配文章源自JAVA秀-https://www.javaxiu.com/39207.html

      查找name为svcA的bean
    2. 如果没有,则按照变量名进行匹配文章源自JAVA秀-https://www.javaxiu.com/39207.html

      查找name为svc的bean
  3. 匹配不到,则报错。(@Autowired(required=false),如果设置requiredfalse(默认为true),则注入失败时不会抛出异常)文章源自JAVA秀-https://www.javaxiu.com/39207.html

@Inject

在 Spring 的环境下,@Inject@Autowired 是相同的,因为它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor来处理的。文章源自JAVA秀-https://www.javaxiu.com/39207.html

Spring官方为什么建议构造器注入?文章源自JAVA秀-https://www.javaxiu.com/39207.html

@Inject是 JSR-330 定义的规范,如果使用这种方式,切换到Guice也是可以的。文章源自JAVA秀-https://www.javaxiu.com/39207.html

“Guice 是 google 开源的轻量级 DI 框架”文章源自JAVA秀-https://www.javaxiu.com/39207.html

如果硬要说两个的区别,首先@Inject是 Java EE 包里的,在 SE 环境需要单独引入。另一个区别在于@Autowired可以设置required=false@Inject并没有这个属性。文章源自JAVA秀-https://www.javaxiu.com/39207.html

@Resource

@Resource是 JSR-250 定义的注解。Spring 在 CommonAnnotationBeanPostProcessor实现了对JSR-250的注解的处理,其中就包括@Resource文章源自JAVA秀-https://www.javaxiu.com/39207.html

Spring官方为什么建议构造器注入?文章源自JAVA秀-https://www.javaxiu.com/39207.html

@Resource有两个重要的属性:nametype,而Spring 将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。文章源自JAVA秀-https://www.javaxiu.com/39207.html

装配顺序:文章源自JAVA秀-https://www.javaxiu.com/39207.html

  1. 如果同时指定了nametype,则从 Spring 上下文中找到唯一匹配的 bean 进行装配,找不到则抛出异常。文章源自JAVA秀-https://www.javaxiu.com/39207.html

  2. 如果指定了name,则从上下文中查找名称(id)匹配的 bean 进行装配,找不到则抛出异常。文章源自JAVA秀-https://www.javaxiu.com/39207.html

  3. 如果指定了type,则从上下文中找到类型匹配的唯一 bean 进行装配,找不到或是找到多个,都会抛出异常。文章源自JAVA秀-https://www.javaxiu.com/39207.html

  4. 如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。文章源自JAVA秀-https://www.javaxiu.com/39207.html

Field injection is not recommended

在使用 IDEA 进行 Spring 开发的时候,当你在字段上面使用@Autowired注解的时候,你会发现 IDEA 会有警告提示:文章源自JAVA秀-https://www.javaxiu.com/39207.html

“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". ”文章源自JAVA秀-https://www.javaxiu.com/39207.html

文章源自JAVA秀-https://www.javaxiu.com/39207.html

Spring官方为什么建议构造器注入?文章源自JAVA秀-https://www.javaxiu.com/39207.html

翻译过来就是这个意思:文章源自JAVA秀-https://www.javaxiu.com/39207.html

“不建议使用基于 field 的注入方式。Spring 开发团队建议:在你的Spring Bean 永远使用基于constructor 的方式进行依赖注入。对于必须的依赖,永远使用断言来确认。”文章源自JAVA秀-https://www.javaxiu.com/39207.html

比如如下代码:文章源自JAVA秀-https://www.javaxiu.com/39207.html

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

将光标放到@Autowired处,使用Alt + Enter 快捷进行修改之后,代码就会变成基于 Constructor 的注入方式,修改之后:文章源自JAVA秀-https://www.javaxiu.com/39207.html

@Servicepublic 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")来确认。文章源自JAVA秀-https://www.javaxiu.com/39207.html

修正这个警告提示固然简单,但是我觉得更重要是去理解为什么 Spring 团队会提出这样的建议?直接使用这种基于 field 的注入方式有什么问题?文章源自JAVA秀-https://www.javaxiu.com/39207.html

首先我们需要知道,Spring 中有这么3种依赖注入的方式:文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • 基于 field 注入(属性注入)文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • 基于 setter 注入文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • 基于 constructor 注入(构造器注入)文章源自JAVA秀-https://www.javaxiu.com/39207.html

1. 基于 field 注入

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

@Autowiredprivate Svc svc;

2. 基于 setter 方法注入

通过对应变量的setXXX()方法以及在方法上面使用注解,来完成依赖注入。比如:文章源自JAVA秀-https://www.javaxiu.com/39207.html

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

“ 注:在 Spring 4.3 及以后的版本中,setter 上面的 @Autowired 注解是可以不写的。”文章源自JAVA秀-https://www.javaxiu.com/39207.html

文章源自JAVA秀-https://www.javaxiu.com/39207.html

3. 基于 constructor 注入

将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。比如:文章源自JAVA秀-https://www.javaxiu.com/39207.html

private final Svc svc;    @Autowiredpublic HelpService(@Qualifier("svcB") Svc svc) {    this.svc = svc;}

“ 在 Spring 4.3 及以后的版本中,如果这个类只有一个构造方法,那么这个构造方法上面也可以不写 @Autowired 注解。”文章源自JAVA秀-https://www.javaxiu.com/39207.html

文章源自JAVA秀-https://www.javaxiu.com/39207.html

基于 field 注入的好处

正如你所见,这种方式非常的简洁,代码看起来很简单,通俗易懂。你的类可以专注于业务而不被依赖注入所污染。你只需要把@Autowired扔到变量之上就好了,不需要特殊的构造器或者set方法,依赖注入容器会提供你所需的依赖。文章源自JAVA秀-https://www.javaxiu.com/39207.html

基于 field 注入的坏处

“成也萧何败也萧何”文章源自JAVA秀-https://www.javaxiu.com/39207.html

基于 field 注入虽然简单,但是却会引发很多的问题。这些问题在我平常开发阅读项目代码的时候就经常遇见。文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • 容易违背了单一职责原则 使用这种基于 field 注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,普通的开发者很可能会无意识地给一个类添加很多的依赖。但是当使用构造器方式注入,到了某个特定的点,构造器中的参数变得太多以至于很明显地发现 something is wrong。拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则(SRP:Single responsibility principle)。文章源自JAVA秀-https://www.javaxiu.com/39207.html

    “这个问题在我司的项目代码真的很常见。”文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • 依赖注入与容器本身耦合文章源自JAVA秀-https://www.javaxiu.com/39207.html

    依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖。换句话说,这个类应该是一个简单的 POJO(Plain Ordinary Java Object)能够被单独实例化并且你也能为它提供它所需的依赖。文章源自JAVA秀-https://www.javaxiu.com/39207.html

    这个问题具体可以表现在:文章源自JAVA秀-https://www.javaxiu.com/39207.html

    • 你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化,这更像是集成测试文章源自JAVA秀-https://www.javaxiu.com/39207.html

    • 你的类和依赖容器强耦合,不能在容器外使用文章源自JAVA秀-https://www.javaxiu.com/39207.html

    • 不能使用属性注入的方式构建不可变对象(final 修饰的变量)文章源自JAVA秀-https://www.javaxiu.com/39207.html

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.”文章源自JAVA秀-https://www.javaxiu.com/39207.html

简单来说,就是文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • 强制依赖就用构造器方式文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • 可选、可变的依赖就用 setter 注入文章源自JAVA秀-https://www.javaxiu.com/39207.html

    当然你可以在同一个类中使用这两种方法。构造器注入更适合强制性的注入旨在不变性,Setter 注入更适合可变性的注入。文章源自JAVA秀-https://www.javaxiu.com/39207.html

让我们看看 Spring 这样推荐的理由,首先是基于构造方法注入,文章源自JAVA秀-https://www.javaxiu.com/39207.html

“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.”文章源自JAVA秀-https://www.javaxiu.com/39207.html

Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任。文章源自JAVA秀-https://www.javaxiu.com/39207.html

而对于基于 setter 的注入,他们是这么说的:文章源自JAVA秀-https://www.javaxiu.com/39207.html

“ 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.”文章源自JAVA秀-https://www.javaxiu.com/39207.html

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

 

小结

以上就是本文的所有内容,希望阅读本文之后能让你对 Spring 的依赖注入有更深的理解。文章源自JAVA秀-https://www.javaxiu.com/39207.html

参考

  • Setter-based dependency injection文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • Field Dependency Injection Considered Harmful文章源自JAVA秀-https://www.javaxiu.com/39207.html

  • IDEA 警告 Field injection is not recommended文章源自JAVA秀-https://www.javaxiu.com/39207.html

<END>文章源自JAVA秀-https://www.javaxiu.com/39207.html

推荐阅读:文章源自JAVA秀-https://www.javaxiu.com/39207.html

令人笑喷的56个代码注释,最后几个老衲实在憋不住了。。。文章源自JAVA秀-https://www.javaxiu.com/39207.html

面试官问我:一个 TCP 连接可以发多少个 HTTP 请求?我竟然回答不上来...文章源自JAVA秀-https://www.javaxiu.com/39207.html

最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

文章源自JAVA秀-https://www.javaxiu.com/39207.html

获取方式:点个「在看」,点击上方小卡片,进入公众号后回复「面试题」领取,更多内容陆续奉上。文章源自JAVA秀-https://www.javaxiu.com/39207.html

朕已阅 Spring官方为什么建议构造器注入?文章源自JAVA秀-https://www.javaxiu.com/39207.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定