当前位置:首页 > 技术交流 > Spring字段注入存在哪些问题,你知道吗?

Spring字段注入存在哪些问题,你知道吗?

2021/08/24 17:05 分类: 技术交流 浏览:0

昨天有个同学面试回来像我求助,说面试官问他Spring字段注入存在什么问题,他当时没有回答上来。

是的,字段注入是我们最常使用的注入方式,但是却一直存在着隐藏着的问题。这个问题其实在使用IDEA的时候已经提示我们了,只是打分时候我们视而不见,或者明明知道却选择了忽视。

那我们来仔细看看吧,这是我们经常使用的一个场景,在一个Controller里注入需要使用的Service

一般我们都是在这个字段上打上Autowired注解,这样就实现了字段注入。

 

 

代码很简单,但是你是否注意到 IDEA 给我们在 @Autowired注解下那提示的波浪线呢?

我们把鼠标移上去看一下:

 

Field injection is not recommended

英文稍微好一点的同学已经知道是什么意思了,这是Spring官方不建议我们这样用啊。

英文稍微没有那么好的也没有关系,我们利用翻译工具看一下:

是的,Spring官方不建议我们使用字段注入的方式,并且建议我们换一种方式,既使用构造方法注入。

 

那么疑问来了,这是为什么呢?

这就要回到,咱们最开始的那道面试题了,Spring字段注入存在哪些问题呢?

通过 @Autowired 注解配合字段注入的实现方式,非常简单而直接,代码的可读性也很强。

事实上,字段注入是三种注入方式中最常用、也是最容易使用的一种。但它也是三种注入方式中最应该避免使用的。

原因有三点:

1.对象的外部可见性

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

在前面的 CourseController 类中,我们通过定义一个私有变量 ICourseService 来注入该接口的实例。显然,这个实例只能在 CourseController 类中被访问,脱离了容器环境我们无法访问这个实例。

既,如果我们不是通过Spring容器,创建CourseController 的实例,而是自己直接new对象的方式创建。

那么,结果就是一个 NullPointerException。

原因就在于无法在 CourseController 的外部实例化 ICourseService 对象。

采用字段注入,类与容器的耦合度过高,我们无法脱离容器来使用目标对象。这种做法实际上是不符合 JavaBean 开发规范的,而且可能一直无法发现空指针异常的存在。

 

2.循环依赖

字段注入的第二个问题,是可能导致潜在的循环依赖。

我们来看下面这段代码:

 

这里的 ClassA 和 ClassB 发生了循环依赖。

上述代码在 Spring 中是合法的,容器启动时并不会报任何错误,而只有在使用到具体某个 ClassA 或 ClassB 时才会报错。 而这个时候,往往为时已晚。

 

3.无法保证注入对象不可变

使用字段注入的方式,我们无法设置需要注入的对象为 final,也无法注入那些不可变对象。

这是因为字段注入的对象必须在类实例化后在进行实例,而final修饰的对象必须提前到对象声明的时候或者在构造方法中实例化。

 

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

 

在面试中,针对字段注入,请记住它主要的三点缺陷:

不具备外部可见性、会导致循环依赖,以及无法注入不可变对象。

 

那么,我们有什么办法解决这些问题呢?

这就要回到另一道经典的面试题上来了,如何在 Spring 中注入对象呢?Spring 的依赖注入有哪些呢?

这道题对于我们的同学来说还是比较简单的。

Spring 为开发人员提供了三种不同的依赖注入类型,分别是字段注入构造器注入Setter 方法注入

既然字段注入有问题,那么我们来看一看,其他两种注入方式呢。

 

1.构造方法注入

关于构造器注入,面试中往往会以这样的形式考察你:

构造器是 Spring 官方推荐的依赖注入类型,你知道它有哪些特性吗?

或者换种问法,构造器注入相比字段注入的优势在哪里?

构造器注入的形式也很简单,就是通过类的构造函数来完成对象的注入,示例代码如下所示:

 

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

关于构造器注入,我也建议你引用 Spring 官方文档来向面试官解释它的功能特性。在 Spring 官方文档中有这样一段话:

这段话的核心意思在于:构造器注入能够保证注入的组件不可变,并且确保需要的依赖不为空。

这里的组件不可变也就意味着你可以使用 final 关键词来修饰所依赖的对象,而依赖不为空是指所传入的依赖对象肯定是一个实例对象,避免出现空指针异常。

 

同时,基于构造器注入,如果存在前面介绍的 ClassA 和 ClassB 之间的循环依赖关系,我们会这样注入对象:

 

那么,在 Spring 容器启动的时候,就会抛出一个循环依赖异常,从而提醒你避免循环依赖。

 

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

但是,构造器注入就没有问题了吗?

当构造函数中存在较多依赖对象的时候,大量的构造器参数会让代码显得比较冗长。

假设一个类的构造器需要 10 个参数,那么我们想要使用这个类时,就需要事先准备好这 10 个参数,并严格按照构造器指定的顺序一一进行传入。

那么,无论从代码的可读性还是可维护角度而言,这都不是很符合最佳实践。

这时候就可以引入 Setter 方法注入。

 

2.Setter 方法注入

Setter 方法注入和构造器注入看上去有点类似,而且它比构造函数更具可读性。

我们可以把多个依赖对象分别通过 Setter 方法逐一进行注入。

 

另一方面,Setter 方法可以很好解决应用程序中的循环依赖问题,如下所示,通过 Setter 方法注入的ClassA 和 ClassB 代码是可以正确执行的:

 

最后,通过 Setter 注入,可以对依赖对象进行多次重复注入,这在构造器注入中是无法实现的。

 

概括起来就是:

*构造器注入适用于强制对象注入,注入的对象是不可变的

*Setter 注入适合于可选对象注入,可以解决循环依赖问题

*字段注入应该避免,对象无法脱离 Spring容器而独立运行。

 

好了,现在你能回答出三种依赖注入类型的相关内容了,依赖注入用得好,Spring 框架面试轻松搞定。

优秀啊,骚年!

#标签:Spring,字段注入