你还在用@Autowired吗

看标题是不是吓一跳,用了好多年的@Autowired用错了吗?没那么夸张,本篇仅仅是讨论一下我们Spring中最常用的依赖注入方式,目前注入方式有三种,分别是:构造函数注入、方法注入、属性注入。我们来看一小段代码

public class HelloController {
  
        @Autowired
    private BeanA a;
    
    private BeanB b;

    @Autowired
    public void setB(BeanB b) {
        this.b = b;
    }

    private final BeanC c;
    
//    @Autowired
    public HelloController(BeanC c) {
        this.c = c;
    }
}

是不是非常熟悉,让我猜一下,大多数人用的是哪一种,我猜肯定是第一种属性注入方式,因为我在学Java刚入门时也一直在用第一种。那为什么标题说你还在用@Autowired吗,是不需要了?还是有替代方案了,还是什么。如果还有不知道@Autowired是作啥的同学请自己科普一下,其实在我写这篇文章之前,我也不知道@Autowired的详情,我再提两个注解,@Required @Inject ,有没有同学用过,我自学Java时候刚刚接触Spring时还是用的Required,当年记得还讨论过 用@Autowired 还是@Required,也查询过资料,但那时没有记录的习惯,这也不是本篇讨论的重点。那重点是啥,现在如果不用@Autowired用什么,为什么不用了,有什么区别。读完本篇文章就可以学到。

还是先看上面的那段代码,有没有发现第三种方法构造函数注入方式的@Autowired是被注释掉的,会不会有疑问,注释掉之后注入方法还能成功吗?这个问题,或者说这类问题,我个人学习时都会有比较明确的方法,就是《官方文档》,我们一起看一下Spring Boot 2.0的

17. Spring Bean和依赖注入

您可以自由使用任何标准的Spring Framework技术来定义bean及其注入的依赖项。为简单起见,我们经常发现使用 @ComponentScan(查找您的bean)和使用@Autowired(进行构造函数注入)效果很好。

如果按照上面的建议构造代码(将应用程序类放在根包中),则可以添加@ComponentScan任何参数。您的所有应用程序组件(的@Component@Service@Repository@Controller等)自动注册为春豆。

以下示例显示了一个@Service使用构造函数注入来获取所需RiskAssessorBean的Bean:

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DatabaseAccountService implements AccountService {

  private final RiskAssessor riskAssessor;

  @Autowired
  public DatabaseAccountService(RiskAssessor riskAssessor) {
      this.riskAssessor = riskAssessor;
  }
  // ...
}

如果bean具有一个构造函数,则可以省略@Autowired,如以下示例所示:

@Service
public class DatabaseAccountService implements AccountService {

  private final RiskAssessor riskAssessor;

  public DatabaseAccountService(RiskAssessor riskAssessor) {
      this.riskAssessor = riskAssessor;
  }
  // ...
}

请注意,使用构造函数注入如何将该riskAssessor字段标记为final,指示该字段 随后无法更改。

也就是官方推荐的使用方式目前是@Autowired注解的构造函数注入,并且@Autowired可以省略。不知道大家有没有写过非Spring框架的Java程序,如果有,可以回忆一下,构造函数用的多吗?为什么用?这个问题留给小伙伴们自己探索。我们继续讨论,为什么要有三种注入方法,什么时候用,优缺点是什么。

为了探索这个问题,我在网上科普了一下相关资料,首先要搞明白问题,就需要从根源出发,有人喜欢看源码,搞清楚原理,而我喜欢看文档,关于这件事,我第一个疑问就是@Autowired是何时出现的,从Spring1.0开始翻,2.0->3.0->2.5,终于让我在Spring2.5的文档中找到了他的出生信息。

从Spring 2.5开始,您可以依赖BeanFactory的自动装配 作为实现BeanFactoryAware 接口的另一种选择。现在,“传统” 构造函数byType自动装配模式(如第3.3.5节“自动装配协作器”一节中所述)能够分别为构造函数参数或setter方法参数提供BeanFactory类型的依赖项 。为了获得更大的灵活性(包括自动装配字段和使用多个参数方法的能力),请考虑使用新的基于注释的自动装配功能。在这种情况下,BeanFactory只要有问题的字段,构造函数或方法带有@Autowired批注,它将自动连接到期望BeanFactory类型 的字段,构造函数参数或方法参数。有关更多信息,请参见标题为第3.10.1节“ @Autowired”的部分。

而且最早出现时,@Autowired并不支持属性注入方式,那没有@Autowired之前就是另外两种,并且1.0时官方推荐的是Setter方法注入,到了2.0官方就一直是推荐构造函数注入了。有兴趣的同学可以看一卡这篇文章,较早版本的就不再讨论了。

https://spring.io/blog/2007/07/11/setter-injection-versus-constructor-injection-and-the-use-of-required/

继续聊@Autowired,他是从什么时候开始大量使用的,Spring3.0,这时Spring支持了@Autowired给属性使用,但官方并没有推荐使用,到了Spring4.3时,官方又对构造函数进行了说明,当一个类只有一个构造函数时,可以省略@Autowired的注释。那么为什么@Autowired 注解属性的形势还会一直存在并延续至今呢,我通过与朋友讨论时获知了一部分原因,经过写代码尝试和网络上的科普最终汇总了几种情况

@Autowired 属性注入形式优点:

1、可以在单例模式下允许循环依赖注入的存在,即,解决了循环依赖注入的问题。如果有业务必须使用循环依赖注入,可以考虑使用。

2、使用简单直接给属性注解即可,代码量少。

3、使代码整洁,美观。

@Autowired 属性注入形式缺点:

1、不允许声明不可变域,也就是无法对属性进行 final 修饰。

2、容易违反单一职责设计原则,举个例子,一个类使用10个以上的@Autowired,代码看起来并无异常,但一个有10个参数的构造函数,已经违反了一个良好的代码规范,从而考虑是否应该优化该类的使用职责。

3、注入顺序靠后,容易出现空指针调用,举个例子,被注入的类用构造函数给其他属性赋值时,由于注入属性还为被反射注入,造成空指针异常。

4、与依赖注入容器紧密耦合,即,在离开Spring容器外,代码将失去作用,例如单元测试。

5、没有对依赖进行检查

然后再一一分析一下优点,其中比较有争议的就是第一点,我个人觉得第一点并不算是优点,循环依赖这种问题,如果没有特殊情况,从架构设计上讲本身就是违反原则的一种情况,应该即时检查并发现错误的存在并排除此类问题(构造器方法,和Setter方法都可以做到而构造器可以在编译时发现,Setter是在运行时发现),不用说肯定是编译时发现更合适。

其他两点都趋向于可读性和美观方面的优点,其实通过同事的启发构造器方式也可以做到同样的效果,比如利用lombok的注解@AllArgsConstructor, 不过这样会隐藏实现,不熟悉的人会造成额外的阅读麻烦,并不推荐。

@AllArgsConstructor
@RestController
public class HelloController {

    private final BeanA a;
    private final BeanB b;
    private final BeanC c;

    @GetMapping("/hi")
    public String sayHi() {
        a.sayHi();
        return "Hi";
    }
}

总结

其实@Autowired属性注入还是Setter注入或者构造函数注入,在大多数情况下都不会是问题,所以其实根据个人习惯就好,另外想说的就是多关注一下所用技术的官方资料,更新,补丁等问题。最后放出Spring5的原文翻译。

基于构造函数或基于setter的DI?

由于可以混合使用基于构造函数的DI和基于setter的DI,因此,将构造函数用于强制性依赖项,将setter方法或配置方法用于可选性依赖项是一个很好的经验法则。请注意,可以 在setter方法上使用@Required批注,以使该属性成为必需的依赖项。但是,最好使用带有参数的程序验证的构造函数注入。

Spring团队通常提倡使用构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保不存在必需的依赖项null。此外,注入构造函数的组件始终以完全初始化的状态返回给客户端(调用)代码。附带说明一下,大量的构造函数自变量是一种不好的代码味,这表明该类可能承担了太多的职责,应进行重构以更好地解决关注点分离问题。

Setter注入主要应仅用于可以在类中分配合理的默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法可使该类的对象在以后重新配置或重新注入。因此,通过JMX MBean进行管理是用于setter注入的引人注目的用例。

使用最适合特定班级的DI风格。有时,在处理您没有源代码的第三方类时,将为您做出选择。例如,如果第三方类未公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。

循环依赖

如果主要使用构造函数注入,则可能会创建无法解决的循环依赖方案。

例如:类A通过构造函数注入需要类B的实例,而类B通过构造函数注入需要类A的实例。如果您将A类和B类的bean配置为相互注入,则Spring IoC容器会在运行时检测到此循环引用,并抛出 BeanCurrentlyInCreationException

一种可能的解决方案是编辑某些类的源代码,这些类的源代码由设置者而不是构造函数来配置。或者,避免构造函数注入,而仅使用setter注入。换句话说,尽管不建议这样做,但是可以使用setter注入配置循环依赖关系。

与典型情况(没有循环依赖关系)不同,bean A和bean B之间的循环依赖关系迫使其中一个bean在完全初始化之前被注入另一个bean(经典的“养鸡和养猪”方案)

注意
只有一个每类注释构造函数 可以作为标记要求,但多个非必需的构造函数可以被注解。在这种情况下,每个候选对象都将被考虑在内,Spring将使用 最贪婪的构造函数,该构造函数可以满足其依存关系,即参数数量最多的构造函数。@Autowired建议在注释上使用的 必填属性 @Required。将 所需的属性表示不需要属性自动装配的目的,如果它不能自动装配的属性被忽略。@Required另一方面,它更强大,因为它可以强制执行通过容器支持的任何方式设置的属性。如果未注入任何值,则会引发相应的异常。

文献资料

https://docs.spring.io/spring/docs/2.0.0/reference/beans.html#beans-factory-autowire

https://docs.spring.io/spring/docs/2.5.0/reference/beans.html#beans-autowired-annotation

https://docs.spring.io/spring/docs/3.0.0.RELEASE/reference/htmlsingle/#beans-autowired-annotation

https://docs.spring.io/spring/docs/5.3.0-M1/spring-framework-reference/core.html#beans-dependencies

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

你可能感兴趣的:(你还在用@Autowired吗)