当前位置: 首页 > 科技观察

终于搞明白了:Spring为什么推荐构造函数注入?

时间:2023-03-21 14:21:47 科技观察

#前言本章内容主要是讨论Spring开发过程中依赖注入的几个知识点。有兴趣的读者可以先看看下面的问题:@Autowired、@Resource、@Inject三种注解的区别你在使用@Autowired的时候,有没有出现不推荐Fieldinjection的警告?你知道为什么吗?Spring依赖注入有哪些方式?官方推荐是什么?如果你理解了以上所有问题,那么我个人认为你的开发体验应该是不错的#128077;。接下来我们依次回答以上问题,总结知识点。#@Autowired、@Resource、@Inject三种注解的区别Spring支持使用@Autowired、@Resource、@Inject三种注解进行依赖注入。下面介绍一下这三个注解的区别。@Autowired@Autowired为Spring框架提供注解,需要导入包org.springframework.beans.factory.annotation.Autowired。下面是一个简单解释的示例代码:.println("hello,thisisserviceB");}}@ServicepublicclassSvcCimplementsSvc{@OverridepublicvoidsayHello(){System.out.println("hello,thisisserviceC");}}测试类:@SpringBootTestpublicclassSimpleTest{@Autowired//@Qualifier("svcA")Svcsvc;@Testvoidrc(){Assertions.assertNotNull(svc);svc.sayHello();}}组装顺序:1.根据类型在上下文中找到匹配的bean,找到类型为Svc的bean2.如果有多个bean,则根据名称匹配1.如果有@Qualifier注解,则根据@Qualifier指定的名称进行匹配,找到名称为svcA的bean3.如果没有,则根据变量名匹配到找到名字为svc4的bean,如果没有匹配到,repo会报错保留。(@Autowired(required=false),如果你设置required为false(默认为true),注入失败时不会抛出异常)@Inject在Spring环境下,@Inject和@Autowired是一样的,因为它们的Dependency注入是使用AutowiredAnnotationBeanPostProcessor处理的。@Inject是JSR-330定义的规范。如果使用这种方法,也可以切换到Guice。Guice是Google开源的轻量级DI框架。如果硬要区分两者,首先@Inject包含在JavaEE包中,需要在SE环境中单独引入。另一个区别是@Autowired可以设置required=false而@Inject没有这个属性。@Resource@Resource是JSR-250定义的注解。Spring在CommonAnnotationBeanPostProcessor中实现了对JSR-250注解的处理,包括@Resource。@Resource有两个重要的属性:name和type,Spring将@Resource注解的name属性解析为bean的名称,type属性解析为bean的类型。组装顺序:如果同时指定name和type,则从Spring上下文中寻找唯一匹配的bean进行组装,找不到则抛出异常。如果指定了名称,则从上下文中查找具有匹配名称(id)的bean进行组装,如果找不到则抛出异常。如果指定了类型,则从上下文中找到唯一与该类型匹配的bean以进行组装。如果没有找到或找到多个bean,将抛出异常。如果既不指定name也不指定type,则默认按照byName进行组装;如果不匹配,则按照byType进行组装。#IDEA提示不推荐Fieldinjection使用IDEA进行Spring开发时,在字段上使用@Autowired注解时,会发现IDEA会有警告提示:FieldinjectionisnotrecommendedInspectioninfo:SpringTeamRecommends:"始终在bean中使用基于构造函数的依赖注入。始终对强制依赖使用断言”。翻译的意思是:不推荐基于字段的注入。Spring开发团队建议您始终在SpringBean中使用基于构造函数的依赖注入。始终对所需的依赖项使用断言。例如下面的代码:@ServicepublicclassHelpService{@Autowired@Qualifier("svcB")privateSvcsvc;publicvoidsayHello(){svc.sayHello();}}publicinterfaceSvc{voidsayHello();}@ServicepublicclassSvcBimplementsSvc{@OverridepublicvoidsayHello(){System.out.println("hello,thisisserviceB");}}将光标置于@Autowired,使用Alt+Enter快速修改代码,代码就会变成基于Constructor的注入方式。修改后:@ServicepublicclassHelpService{privatefinalSvcsvc;@AutowiredpublicHelpService(@AutowiredpublicHelpService(@Qualifier("svcB")Svcsvc){//Assert.notNull(svc,"svcmustnotbenull");this.svc=svc;}publicvoidsayHello(){svc.sayHello();}}如果按照Spring团队的建议,如果svc是必要的依赖,应该使用Assert.notNull(svc,"svcmustnotbenull")来确认。很容易修复这个警告提示,但是我觉得更重要的是理解Spring团队为什么会提出这样的建议?直接使用这种基于字段的注入有什么问题呢?首先我们要知道Spring中依赖注入的方式一共有三种:字段基于构造函数的注入(propertyinjection)基于Setter的注入基于构造函数的注入(constructorinjection)1.基于字段的注入所谓基于字段的注入,就是在bean变量上使用注解的依赖注入。本质上,它是通过反射直接注入到场中的。这是我平时开发中见得最多,也是最熟悉的方式。同时也是Spring团队不推荐的方式。例如:@AutowiredprivateSvcsvc;2、在setter方法注入的基础上,使用对应变量的setXXX()方法,并在方法上使用注解,完成依赖注入。例如:privateHelperhelper;@AutowiredpublicvoidsetHelper(Helperhelper){this.helper=helper;}复制代码注意:在Spring4.3及之后的版本中,setter上的@Autowired注解可以省略。3.基于构造函数注入,将所有需要的依赖放在带注解的构造函数的参数中,并在构造函数中完成相应变量的初始化。该方法是基于构造函数的注入。例如:privatefinalSvcsvc;@AutowiredpublicHelpService(@Qualifier("svcB")Svcsvc){this.svc=svc;}在Spring4.3及之后的版本中,如果这个类只有一个构造方法,那么这个构造方法也可以不写@Autowired注解。基于字段注入的好处可以看到,这种方法非常简洁,代码看起来也非常简单易懂。您的类可以专注于业务而不会被依赖注入污染。您只需在变量上添加@Autowired,不需要特殊的构造函数或设置器,依赖注入容器将提供您需要的依赖项。基于字段的注入的缺点SuccessandXiaoHefailureandXiaoHe基于字段的注入虽然简单,但是会带来很多问题。这些问题是我平时开发和阅读项目代码时经常遇到的。很容易违反单一职责原则。使用这种基于字段注入的方式,添加依赖非常简单。即使你的类中有十几个依赖项,你可能会觉得没有问题。普通开发者可能会不自觉地给A类添加很多依赖。但是当使用构造函数注入时,在某个时刻,构造函数中的参数变得如此之多,以至于很明显出了问题。依赖太多通常意味着你的类要承担更多的责任,这显然违反了单一责任原则(SRP:Singleresponsibilityprinciple)。这个问题在我们公司的项目代码中确实很常见。依赖注入耦合到容器本身依赖注入框架的核心思想之一是容器管理的类不应该依赖于容器使用的依赖项。也就是说,这个类应该是一个简单的POJO(PlainOrdinaryJavaObject),可以单独实例化,你也可以为它提供它需要的依赖。这个问题具体可以表现在:你的类和依赖的容器是强耦合的,你不能在容器外使用你的类。您不能绕过反射(例如,单元测试时)进行实例化。必须通过依赖容器来实例化,更像是一个集成测试,不能使用属性注入构建不可变对象(final修饰变量)Spring开发组的建议既然可以混合constructor-based和setter-basedDI,那么它一个很好的经验法则是对强制依赖项使用构造函数,对可选依赖项使用setter方法或配置方法。简单来说,如果强制依赖是可选的,就使用构造方法,对于变量依赖,使用setter注入。当然,你可以在同一个类中使用这两个方法。构造函数注入更适合针对不可变性的强制注入,setter注入更适合可变性注入。下面看看Spring推荐的原因。第一种是基于构造函数注入。Spring团队普遍提倡构造函数注入,因为它使人们能够将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为旁注,大量的构造函数参数是一种糟糕的代码味道,暗示该类可能有太多责任,应该重构以更好地解决关注点的适当分离。Spring团队提倡使用基于构造函数的注入,因为一方面可以将依赖注入到一个不可变变量(注意:final修饰的变量)中,另一方面也可以保证取值这些变量不会为空。另外,通过构造方法完成依赖注入的组件(注:如各种服务)可以保证在被调用时完全就绪。同时,从代码质量的角度来看,一个巨大的构造函数通常代表一种代码味道,类可能承担了太多的责任。而对于基于setter的注入,他们是这样说的:Setter注入应该主要只用于可选的依赖项,这些依赖项可以在类内分配合理的默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是setter方法使该类的对象可以在以后重新配置或重新注入。基于setter的注入应该只用于注入非必需的依赖,并且应该为这种依赖提供一个类合理的默认值。如果使用setter来注入所需的依赖项,将会有太多的空检查淹没代码。使用setter注入的一个优点是可以轻松更改或重新注入这种依赖关系。#总结以上就是本文的全部内容。希望阅读本文后,您对Spring的依赖注入有更深入的了解。