在IDEA中使用Spring框架时,你有没有遇见过这样的一个提示:Field injection is not recommended?
这里可能有些小伙伴遇见过,也有些没有遇见过,没有关系,我们来解释一下这个提示意思:不推荐使用字段注入
注入类型没遇见的小伙伴不要着急扣问号,我们进入正题
我们在使用Spring框架进行开发时,不可避免的要进行依赖注入DI(Dependency Injection),也就是把实例从Spring容器中取出来进行使用。Spring的依赖注入方式主要有三种,分别为Constructor、Setter和Field, Field 注入是使用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; }
这里的 ClassA 和 ClassB 发生了循环依赖,上述代码在 Spring 中是合法的,容器启动时并不会报任何的错误,只有在使用到具体某个 ClassA 或 ClassB 时才会报错
字段注入问题三:无法设置需要注入的对象为 final ,也无法注入那些不可变的对象
因为字段必须在类实例化时进行实例化!
基于以上三点,无论是 IDEA ,还是 Spring 官方都不推荐开发人员使用字段/Field 注入这种注入模式,而是推荐构造器注入!
面对字段注入时,请记住它三个缺陷:
-
不具备外部可见性
-
会导致循环依赖
-
无法注入不可变的对象
构造器注入是 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 方法进行注入了
Setterpublic 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 注入应该避免使用,它无法脱离容器而独立存在
三种方式各有利弊,从靠谱程度来说,还是构造器注入更好一些,它能有效避免一些比如循环依赖、空指针等异常的发生。另外,Spring中Bean默认为单例的,有可能会出现线程安全问题,这个时候final就更有必要了,方法没有好坏,主要是看怎么运用,官方也是只是推荐使用构造器的方法,但是其他的方法没有禁止,证明也有他们强势的地方,但是一般情况下最好都是用构造器方法,等有必要或者有一定了解后在做改动。
另外,当有一个依赖有多个实现的使用,推荐使用field注入或者setter注入的方式来指定注入的类型。这是spring官方博客对setter注入方式和构造器注入的比较。
好了,看到这里,各位对Spring依赖注入有了解了吧? 感谢各位小伙伴的观看
这篇字有点多,如果你看着看着有些厌烦了,你可以选择实在无聊的时候再来看看
技术有限,如有错误,还望指出,互相学习,共同进步!
参考连接:
Field Dependency Injection Considered Harmful | Vojtech Ruzicka's Programming Blog
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)