Spring的依赖注入有什么优势?

前言

IDEA中使用Spring框架时,你有没有遇见过这样的一个提示Field injection is not recommended?

 

这里可能有些小伙伴遇见过,也有些没有遇见过,没有关系,我们来解释一下这个提示意思:不推荐使用字段注入

没遇见的小伙伴不要着急扣问号,我们进入正题

注入类型

我们在使用Spring框架进行开发时,不可避免的要进行依赖注入DI(Dependency Injection),也就是把实例从Spring容器中取出来进行使用。Spring的依赖注入方式主要有三种,分别为ConstructorSetterFieldField 注入是使用Spring框架开发中最常用的一种。

Field

@Autowired
private DependencyA dependencyA;
​
@Autowired
private DependencyB dependencyB;
​
@Autowired
private DependencyC dependencyC;
...

那么为什么Spring/IDEA官方不推荐使用Filed注入呢?

如您所见,Field注入的实现方式非常简单直接,代码的可读性也很强。代码易于阅读。 您的类可以只关注重要的内容,而不会被DI(Dependency Injection)样板文件所污染。你只需要把 @Autowired 注解放在字段上面就行了。没有特殊的构造函数或setter,只是为了DI容器提供依赖项。Java本身是非常冗长的,所以任何缩短代码的机会都是受欢迎的,对吧?

事实上,字段注入是三种注入方式中最常见,也是最容易使用的一种。但它也是三种注入方式中最应该避免使用的,就像开篇所说,我们在IDEA 中使用字段注入时会遇到:Field injection is not recommended 的提示!为什么呢?原因有三点

字段注入的最大问题是:对象的外部可见性

假设我们有文稿所示的一个 HealthRecordService接口以及它的实现类

public interface HealthRecordService {  

    void recordUserHealthData();

}​


@Service
public class HealthRecordServiceImpl implements HealthRecordService {    

@Override    

public void recordUserHealthData() {        

    System.out.println("HealthRecordService has been called");  

    }

}

其次我们有定义一个 ClientTest 类,对它进行字段注入

public class ClientTest {
    @Autowired
    private HealthRecordService healthRecordService;
​
    public void recordUserHealthData(){
        healthRecordService.recordUserHealthData();
    }
}

显然这个这个实例只能在 ClientController 类中被访问,脱离了容器环境我们无法访问这个实例,如下文稿所示

public static void main(String[] args) {
    test test = new test();
    test.recordUserHealthData();
}

执行这段代码的结果就是抛出一个 NullPointerException 空指针异常

原因就在于无法在 test 类的外部实例化 HealthRecordService 对象,采用字段注入,类和容器的耦合度过高,我们无法脱离容器来使用目标对象。如果编写测试用例来保障 test 类的正确性,那么想要使用 HealthRecordService 对象,就只能采用反射机制的方式。这种做法,不符合 JavaBean 开发规范的,而且可能一直无法发现 NullPointerException 空指针异常的存在

字段注入的问题二:可能导致潜在的循环依赖

即两个类之间互相进行注入,例如下面文稿所展示的代码示例:

public class ClassA{
    @Autowired
    private ClassB b;
}
​
public class ClassB{
    @Autowired
    private ClassA a;
}

这里的 ClassAClassB 发生了循环依赖,上述代码在 Spring 中是合法的,容器启动时并不会报任何的错误,只有在使用到具体某个 ClassAClassB 时才会报错

字段注入问题三:无法设置需要注入的对象为 final ,也无法注入那些不可变的对象

因为字段必须在类实例化时进行实例化!

基于以上三点,无论是 IDEA ,还是 Spring 官方都不推荐开发人员使用字段/Field 注入这种注入模式,而是推荐构造器注入!

面对字段注入时,请记住它三个缺陷:

  1. 不具备外部可见性

  2. 会导致循环依赖

  3. 无法注入不可变的对象

Constructor

构造器注入是 Spring/ IDEA 官方推荐的依赖注入类型,那么它有哪些特性呢?

构造器注入相比字段注入的优势在哪里?

别急,我们这就进入正题!

构造器注入的形式也很简单,就是通过构造函数来完成对象的注入

public class ClientTest {
    
    private HealthRecordService healthRecordService;
    
    //@Autowired
    public ClientTest(HealthRecordService healthRecordService){
        this.healthRecordService = healthRecordService;
    }
    
    public void recordUserHealthData(){
        healthRecordService.recordUserHealthData();
    }
​
}

如上实例代码所示

可以看到构造器注入能解决对象外部可见性的问题,因为 HealthRecordService 是通过 ClientTest 构造函数进行注入的,所以势必可以脱离 ClientTest 类而独立存在。

关于构造器注入,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. 

这段话的核心意思在于,构造器注入能够保证注入的组件不可变并且确保需要的依赖不为空。这里的组件不为空也就意味着你可以使用 final 关键词来修饰所依赖的对象,而依赖不为空,是指所传入的依赖对象肯定是一个实例对象,避免出现空指针异常

同时基于构造注入,如果存在前面介绍的 ClassA 和 ClassB 之间的循环依赖关系,如文稿中所示,那么在Spring 容器启动时,就会抛出一个循环依赖异常,从而提醒你避免循环依赖异常

如此看来,字段注入的三大问题都可以通过使用构造器注入的方式来解决!

感谢看到这里的小伙伴,讲了这么多废话还是有小伙伴看的 (๑•̀ㅂ•́)و✧)

那难道构造注入就没有弊端了吗?答案是有的。当构造函数中存在较多的依赖对象时,大量的构造器参数会让代码显得非常冗长。

假设一个类的构造器需要n个参数,那么我们想要使用这个类时,就需要事先将这n个参数准备好,并严格按照构造器指定的顺序一 一进行传入,那么无法从代码的可读性还是维护性角度而言,这都不是很符合最佳实践的,这时候,就该我们的 Setter 方法进行注入了

Setter

public class ClientTest {
​
    private HealthRecordService healthRecordService;
​
    //@Autowired
    public void setHealthRecordService(HealthRecordService healthRecordService) {
        this.healthRecordService = healthRecordService;
    }
    ...
    
    public void recordUserHealthData(){
        healthRecordService.recordUserHealthData();
    }
}
​

Setter 方法注入的实现代码如文稿中所示

Setter 方法注入和构造器注入看上去有点类似,但它比构造器更有可读性,因为我们可把多个依赖对象,分别通过 Setter 方法逐一进行注入,而且 Setter 方法注入对于非强制性依赖时,注入很有用。我们可以有选择的注入一部分想注入的依赖对象,换句话说,可以实现按需注入,帮助我们只在需要时注入依赖关系。

另一方面 Setter 方法可以很好解决应用程序中的循环依赖问题,如文稿所示代码是可以正确执行的:

class ClassA{
    private ClassB b;
    public void setB(ClassB b) {
        this.b = b;
    }
}
​
class ClassB{
    private  ClassA a;
    public void setA(ClassA a) {
        this.a = a;
    }
}

请注意,上述代码能够正确执行的前提是 ClassA 和 ClassB 的作用域都是 Singleton (单例模式),最后通过 Setter 方法注入可以对依赖对象进行多次重复的注入,这在构造器注入中是无法实现的

在Spring3.x刚推出的时候,推荐使用注入的就是这种,笔者现在也基本没看到过这种注解方式,写起来麻烦,当初推荐Spring自然也有他的道理,这里我们引用一下Spring当时的原话:

TIP

在Spring3.x刚推出的时候,官方推荐使用 Setter 方法注入。Spring官方目前推荐的是构造器注入

那么后面为什么又换成构造器注入了呢?(喂喂喂,Spring你前一版本还贬低构造器注入,后面就立刻捧人家了不好吧,不过能用于承认自己的错误,才是真正令人称赞的地方吧 (๑•̀ㅂ•́)و✧)

总结

构造器注入适用于强制对象注入

Setter 方法注入适用于可选对象注入

Field 注入应该避免使用,它无法脱离容器而独立存在

三种方式各有利弊,从靠谱程度来说,还是构造器注入更好一些,它能有效避免一些比如循环依赖、空指针等异常的发生。另外,SpringBean默认为单例的,有可能会出现线程安全问题,这个时候final就更有必要了,方法没有好坏,主要是看怎么运用,官方也是只是推荐使用构造器的方法,但是其他的方法没有禁止,证明也有他们强势的地方,但是一般情况下最好都是用构造器方法,等有必要或者有一定了解后在做改动。

另外,当有一个依赖有多个实现的使用,推荐使用field注入或者setter注入的方式来指定注入的类型。这是spring官方博客对setter注入方式和构造器注入的比较

好了,看到这里,各位对Spring依赖注入有了解了吧? 感谢各位小伙伴的观看

这篇字有点多,如果你看着看着有些厌烦了,你可以选择实在无聊的时候再来看看

技术有限,如有错误,还望指出,互相学习,共同进步!

参考连接:

Field Dependency Injection Considered Harmful | Vojtech Ruzicka's Programming Blog

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值