阅读文本大概需要 7 分钟。
昨天有个同学面试回来向我求助,说面试官问他Spring字段注入存在什么问题,他当时没有回答上来。
是的,Spring的字段注入,是我们最常使用的注入方式,但是却一直存在着隐藏着的问题。
这个问题其实在使用IDEA的时候已经提示我们了,只是大部分时候我们视而不见,或者明明知道,却选择了忽视。
现在我们来仔细看看吧,这是我们经常使用的一个场景,在一个Controller里注入需要使用的Service。
一般我们都是在这个字段上打上Autowired注解,这样就实现了字段注入。
代码很简单,但是你是否注意到 IDEA 给我们在 @Autowired注解下那提示的波浪线呢?
我们把鼠标移上去看一下:
英文稍微好一点的同学已经知道是什么意思了,这是Spring官方不建议我们这样用啊。
英文稍微没有那么好的也没有关系,我们利用翻译工具看一下:
是的,Spring官方不建议我们使用字段注入的方式,并且建议我们换一种方式。
哈哈,推荐使用构造方法注入。
那么疑问来了,这是为什么呢?
这就要回到咱们最开始的那道面试题了,Spring字段注入存在哪些问题呢?
是的,通过 @Autowired 注解配合字段注入的实现方式,非常简单而直接,代码的可读性也很强。
而且,事实上在我们的开发过程中,字段注入是三种注入方式中最常用、也是最容易使用的一种。
但它也是三种注入方式中最应该避免使用的。
字段注入的最大问题是就是对象的外部可见性问题。
在前面的 CourseController 类中,我们通过定义一个私有变量 ICourseService 来注入该接口的实例。显然,这个实例只能在 CourseController 类中被访问,脱离了容器环境我们无法访问这个实例。
既,如果我们不是通过Spring容器注入 CourseController 的实例,而是自己直接new对象的方式创建。
那么,结果就是一个 NullPointerException。
原因就在于,无法在 CourseController 的外部实例化 ICourseService 对象。
采用字段注入,类与容器的耦合度过高,我们无法脱离容器来使用目标对象。
这种做法实际上是不符合 JavaBean 开发规范的,而且可能一直无法发现空指针异常的存在。
字段注入的第二个问题,是可能导致潜在的循环依赖。
我们来看下面这段代码:
这里的 ClassA 和 ClassB 实际上就发生了循环依赖。
上述代码在 Spring 中是合法的,容器启动时并不会报任何错误,只有在使用到具体某个 ClassA 或 ClassB 时才会报错。
而这个时候,往往为时已晚。
使用字段注入的方式,我们无法设置需要注入的对象为 final,也无法注入那些不可变对象。
这是因为字段注入的对象必须在类实例化后在进行实例,而final修饰的对象必须提前到对象声明的时候或者在构造方法中实例化。
基于以上三点,无论是 IDEA,还是 Spring 官方,都不推荐我们开发人员使用字段注入这种注入模式,而是推荐构造器注入。
在面试中,针对字段注入,请记住它主要的三点缺陷:
不具备外部可见性、会导致循环依赖,以及无法注入不可变对象。
这就要回到另一道经典的面试题上来了,如何在 Spring 中注入对象呢?Spring 的依赖注入有哪些呢?
这道题对于我们的同学来说还是比较简单的。
Spring 为开发人员提供了三种不同的依赖注入类型,分别是字段注入、构造器注入和 Setter 方法注入
既然字段注入有问题,那么我们来看一看,其他两种注入方式呢。
关于构造器注入,面试中往往会以这样的形式考察你:
构造器是 Spring 官方推荐的依赖注入类型,你知道它有哪些特性吗?
或者换种问法,构造器注入相比字段注入的优势在哪里?
构造器注入的形式也很简单,就是通过类的构造函数来完成对象的注入,示例代码如下所示:
可以看到构造器注入能解决对象外部可见性的问题,因为这里的 ICourseService 是通过 CourseController 构造函数进行注入的,所以势必可以脱离 CourseController 而独立存在。
关于构造器注入,我们也建议你引用 Spring 官方文档来向面试官解释它的功能特性。在 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 容器启动的时候,就会抛出一个循环依赖异常,从而提醒你避免循环依赖。
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'CLassA' defined in file[]:
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'CLassB' defined in file []:
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'CLassA':
Requested bean is currently in creation:
Is there an unresolvable circular reference?
如此看来,字段注入的三大问题,就都可以通过使用构造器注入的方式来解决。
但是,构造器注入就没有问题了吗?显然不是!
当构造函数中存在较多依赖对象的时候,大量的构造器参数会让代码显得比较冗长。
假设一个类的构造器需要多个参数,那么我们想要使用这个类时,就需要事先准备好这些参数,并严格按照构造器指定的顺序一一进行传入。
那么,无论从代码的可读性还是可维护角度而言,这都不是很符合最佳实践。
这时候就可以引入 Setter 方法注入。
Setter 方法注入和构造器注入看上去有点类似,而且它比构造函数更具可读性。
我们可以把多个依赖对象分别通过 Setter 方法逐一进行注入。
另一方面,Setter 方法可以很好解决应用程序中的循环依赖问题,如下所示,通过 Setter 方法注入的ClassA 和 ClassB 代码是可以正确执行的:
而且,通过 Setter 注入,还可以对依赖对象进行多次重复注入,这在构造器注入中是无法实现的。
最后,概括起来就是:
*构造器注入适用于强制对象注入,注入的对象是不可变的
*Setter 注入适合于可选对象注入,可以解决循环依赖问题
*字段注入应该避免,对象无法脱离 Spring容器而独立运行,。
好了,现在你能回答出三种依赖注入类型的相关内容了,依赖注入用得好,Spring 框架面试轻松搞定。
优秀啊,骚年!后期更多优选推文,各种资料、分享猛料放出,关注公众号,获取实时动态:
大家还有什么需求,也可以后台留言给我,公众号上还有其他学习资源哦....
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有