@Autowired依赖注入为啥不推荐了

这几天更新升级了一下java编码神器IDEA,升级完进行日常开发,可能是以前用的IDEA版本比较老旧,升级之后发现之前的日常写法有了个warning提醒。来看图:
warning1
如上图,这就奇怪了,我们经常写的业务层就是service接口层和对应的实现类层进行属性注入的时候都是采用注解进行注入的。这也是springIOC给提供的比较方便的地方。我使用IDEA提供的自动修复提示修复了之后变成采用构造函数的形式进行注入了。
warning2

但是多年面向Spring开发的经验告诉我,使用@Autowired注解进行依赖注入,肯定是没有问题的。但是我的代码洁癖不允许我这么不明不白的留一个警告在这里。所以,带着我的洁癖,和我的好奇心,我开始研究起了这个警告。

警告内容

warning3
我们简单翻译一下自动提示的是啥意思:

不建议直接在字段上进行依赖注入。
Spring 开发团队建议:在Java Bean中永远使用构造方法进行依赖注入。对于必须的依赖,永远使用断言来确认。

我们说明上面的问题之前先回顾几个spring相关的问题:

依赖注入的方式

Spring 有三种依赖注入的方式

  1. 基于属性的注入

这种注入方式就是在bean的变量上使用注解进行依赖注入。本质上是通过反射的方式直接注入到field。这是我平常开发中看的最多也是最熟悉的一种方式。

@Autowired
PushTaskService pushTaskService;
  1. 基于setter方法的注入

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

private static TaskGroupTemplateRepository taskGroupTemplateRepository;
private static TaskGroupService taskGroupService;

@Autowired
public void setTaskGroupTemplateRepository(TaskGroupTemplateRepository taskGroupTemplateRepository,TaskGroupService taskGroupService){
    ExcelListener2.taskGroupTemplateRepository = taskGroupTemplateRepository;
    ExcelListener2.taskGroupService = taskGroupService;
}

说明:在 Spring 4.5 及更高的版本中,setXXX 上面的 @Autowired 注解是可以不写的。

  1. 基于构造方法的注入

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

@Autowired
public ExcelListener(@Qualifier("taskGroupService") TaskGroupService taskGroupService) {
    this.taskGroupService = taskGroupService;
}
@Autowired是干啥的

我们一般开发需要注入属性的时候都会使用的这个注解@Autowired,跟这个注解类似的还有2个,@Resource, @Inject。Spring 支持使用@Autowired, @Resource, @Inject 三个注解进行依赖注入。我们先看一下有啥区别:

@Autowired为Spring框架提供的注解,可以理解是Spring的亲儿子。这里先给出一个示例代码

public interface IndexService {

    void sayHello();
}

@Service
public class IndexServiceImpl implements IndexService {

    @Override
    public void sayHello() {
        System.out.println("hello, this is IndexServiceImpl");
    }

}

@Service
public class IndexServiceImpl2 implements IndexService {

    @Override
    public void sayHello() {
        System.out.println("hello, this is IndexServiceImpl2");
    }

}

测试方法

@SpringBootTest
public class Stest {

    @Autowired
    // @Qualifier("indexServiceImpl2")
    IndexService indexService;

    @Test
    void gooo() {
        Assertions.assertNotNull(indexService);
        indexService.sayHello();
    }

}

我们说明一下匹配bean的过程:

  1. 按照type在上下文中查找匹配,查找type为IndexService的bean
  2. 如果有多个bean,则按照name进行匹配
    • 如果有@Qualifier注解,则按照@Qualifier指定的name进行匹配,查找name为indexServiceImpl2的bean
    • 如果没有,则按照变量名进行匹配。查找name为indexService的bean
  3. 匹配不到,则报错。(@Autowired(required=false),如果设置required为false(默认为true),则注入失败时不会抛出异常)
@Inject是干啥的

在Spring 的环境下,@Inject和@Autowired 是相同的,因为它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor这个后置处理器来处理的。
iShot2021-05-23 21
这两个的区别,首先@Inject是Java EE包里的,在SE环境需要单独引入。另一个区别在于@Autowired可以设置required=false而@Inject并没有这个属性。也有的说@Inject是spring的干儿子。

@Resource是干啥的

@Resource是JSR-250定义的注解。Spring 在 CommonAnnotationBeanPostProcessor实现了对JSR-250的注解的处理,其中就包括@Resource。
这个@Resource有2个属性name和type。在spring中name属性定义为bean的名字,type这是bean的类型。如果属性上加@Resource注解那么他的注入流程是:

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
  2. 如果指定了name,则从上下文中查找名称匹配的bean进行装配,找不到则抛出异常。
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
  4. 如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。

上面的内容都了解了之后我们接下来看为啥IDEA会有个warning提醒。IDEA 提示 Field injection is not recommended。warning提醒的注入方式就是第一种使用属性注解的方式进行注入。

属性注入优点

代码看起来很简单,通俗易懂。你的类可以专注于业务而不被依赖注入所污染。你只需要把@Autowired扔到变量之上就好了方便开发人员进行代码的编写。

属性注入可能出现的问题
  • 问题1

基于 field 的注入,虽然不是绝对禁止使用,但是它可能会带来一些隐含的问题。来我们举个例子:

@Autowired
private Person person;

private String company;

public UserServiceImpl(){
    this.company = person.getCompany();
}

初看起来好像没有什么问题,Person 类会被作为一个依赖被注入到当前类中,同时这个类的 company 属性将在初始化时通过person.getCompany() 方法来获得值。我们尝试运行一下就会发现报错了。其实类似这个问题有人在stackoverflow上提问过,点我跳转

Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Constructor threw exception; nested exception is java.lang.NullPointerException

在执行this.company = person.getCompany();这段代码的时候报了空指针。
出现这个问题的原因是,Java 在初始化一个类时,是按照静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 -> @Autowired 的顺序。所以在执行这个类的构造方法时,person 对象尚未被注入,它的值还是 null。

  • 问题2

使用这种基于 field 注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,如果你一个类注入非常多的其它的对象,拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则。顺便我看了一下我们现在的业务代码这个问题在我们的项目代码中真的很常见。

  • 问题3

这种注入形式就会造成你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化。也就是类和依赖容器强耦合,不能在容器外使用。

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.

其实大概的意思就是2句话

强制依赖就用构造器方式
可选、可变的依赖就用setter注入

spring对采用构造方法注入的说明

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

spring对采用setter方法注入的说明

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

所以我们日常开发过程中要细心往往一个warning就能牵扯出来一个知识点。
qrcode_for_gh_6397763ae9fc_258

作者注:欢迎关注笔者公号,定期分享IT互联网、金融、教育等工作经验心得、人生感悟,欢迎交流。

  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值