Spring构造器注入有多好?

简介: Spring构造器注入有多好?

前言


本章的内容主要是想探讨我们在进行 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();
    }
}


装配顺序:

  1. 按照type在上下文中查找匹配的bean
查找type为Svc的bean



  1. 如果有多个bean,则按照name进行匹配
  1. 如果有@Qualifier注解,则按照@Qualifier指定的name进行匹配
查找name为svcA的bean


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

查找name为svc的bean


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


@Inject


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


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

Guice 是 google 开源的轻量级 DI 框架


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


@Resource


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


68.jpg

@Resource有两个重要的属性:nametype,而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 会有警告提示:68.png

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N5gUVVyD-1629898005080)(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)]


翻译过来就是这个意思:

69.png


比如如下代码:

@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;
}

70.png


3. 基于 constructor 注入


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


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

71.png


基于 field 注入的好处


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


基于 field 注入的坏处


72.png


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


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


依赖注入与容器本身耦合


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


这个问题具体可以表现在:


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


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


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


Spring 开发团队的建议


75.png


简单来说,就是


强制依赖就用构造器方式


可选、可变的依赖就用 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 的依赖注入有更深的理解。


参考


Setter-based dependency injection

Field Dependency Injection Considered Harmful

IDEA 警告 Field injection is not recommended

的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入。


小结

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


参考

  • Setter-based dependency injection
  • Field Dependency Injection Considered Harmful
  • IDEA 警告 Field injection is not recommended
目录
相关文章
|
2月前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
148 3
|
2月前
|
Java 测试技术 程序员
为什么Spring不推荐@Autowired用于字段注入?
作为Java程序员,Spring框架在日常开发中使用频繁,其依赖注入机制带来了极大的便利。然而,尽管@Autowired注解简化了依赖注入,Spring官方却不推荐在字段上使用它。本文将探讨字段注入的现状及其存在的问题,如难以进行单元测试、违反单一职责原则及易引发NPE等,并介绍为何Spring推荐构造器注入,包括增强代码可读性和维护性、方便单元测试以及避免NPE等问题。通过示例代码展示如何将字段注入重构为构造器注入,提高代码质量。
109 1
|
16天前
|
Java Spring
一键注入 Spring 成员变量,顺序编程
介绍了一款针对Spring框架开发的插件,旨在解决开发中频繁滚动查找成员变量注入位置的问题。通过一键操作(如Ctrl+1),该插件可自动在类顶部添加`@Autowired`注解及其成员变量声明,同时保持光标位置不变,有效提升开发效率和代码编写流畅度。适用于IntelliJ IDEA 2023及以上版本。
一键注入 Spring 成员变量,顺序编程
|
7月前
|
XML Java 程序员
Spring6框架中依赖注入的多种方式(推荐构造器注入)
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
111 3
|
2月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
72 1
|
4月前
|
XML Java 数据格式
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
这篇文章是Spring5框架的实战教程,主题是IOC容器中Bean的集合属性注入,通过XML配置方式。文章详细讲解了如何在Spring中注入数组、List、Map和Set类型的集合属性,并提供了相应的XML配置示例和Java类定义。此外,还介绍了如何在集合中注入对象类型值,以及如何使用Spring的util命名空间来实现集合的复用。最后,通过测试代码和结果展示了注入效果。
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
|
4月前
|
缓存 Java 数据库连接
Spring Boot 资源文件属性配置,紧跟技术热点,为你的应用注入灵动活力!
【8月更文挑战第29天】在Spring Boot开发中,资源文件属性配置至关重要,它让开发者能灵活定制应用行为而不改动代码,极大提升了可维护性和扩展性。Spring Boot支持多种配置文件类型,如`application.properties`和`application.yml`,分别位于项目的resources目录下。`.properties`文件采用键值对形式,而`yml`文件则具有更清晰的层次结构,适合复杂配置。此外,Spring Boot还支持占位符引用和其他外部来源的属性值,便于不同环境下覆盖默认配置。通过合理配置,应用能快速适应各种环境与需求变化。
56 0
|
4月前
|
安全 Java 开发者
开发者必看!@Resource与private final的较量,Spring Boot注入技巧大揭秘,你不可不知的细节!
【8月更文挑战第29天】Spring Boot作为热门Java框架,其依赖注入机制备受关注。本文通过对比@Resource(JSR-250规范)和@Autowired(Spring特有),并结合private final声明的字段注入,详细探讨了两者的区别与应用场景。通过示例代码展示了@Resource按名称注入及@Autowired按类型注入的特点,并分析了它们在注入时机、依赖性、线程安全性和单一职责原则方面的差异,帮助开发者根据具体需求选择最合适的注入策略。
196 0
|
5月前
|
缓存 Java Spring
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
|
5月前
|
Java Spring
Spring循环依赖问题之构造器内的循环依赖如何解决
Spring循环依赖问题之构造器内的循环依赖如何解决