1 基于构造函数的依赖注入

Spring 基于构造函数的依赖注入_w3cschoolJ虽然当前有关Spring Framework(5.0.3)的文档仅定义了两种主要的注入类型,但实际上有三种

public class UserServiceImpl implents UserService{private UserDao userDao;@Autowirepublic UserServiceImpl(UserDao userDao){this.userDao = userDao;}
}

在 Spring 4.3 及以后的版本中,如果这个类只有一个构造方法,那么这个构造方法上面也可以不写 @Autowired 注解。


Spring 基于构造函数的依赖注入_w3cschoolJ

这是 TextEditor.java 文件的内容:

package com.tutorialspoint;
public class TextEditor {private SpellChecker spellChecker;public TextEditor(SpellChecker spellChecker) {System.out.println("Inside TextEditor constructor." );this.spellChecker = spellChecker;}public void spellCheck() {spellChecker.checkSpelling();}
}

下面是另一个依赖类文件 SpellChecker.java 的内容:

package com.tutorialspoint;
public class SpellChecker {public SpellChecker(){System.out.println("Inside SpellChecker constructor." );}public void checkSpelling() {System.out.println("Inside checkSpelling." );}
}

以下是 MainApp.java 文件的内容:

package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");TextEditor te = (TextEditor) context.getBean("textEditor");te.spellCheck();}
}

下面是配置文件 Beans.xml 的内容,它有基于构造函数注入的配置:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd"><!-- Definition for textEditor bean --><bean id="textEditor" class="com.tutorialspoint.TextEditor"><constructor-arg ref="spellChecker"/></bean><!-- Definition for spellChecker bean --><bean id="spellChecker" class="com.tutorialspoint.SpellChecker"></bean></beans>

当你完成了创建源和 bean 配置文件后,让我们开始运行应用程序。如果你的应用程序运行顺利的话,那么将会输出下述所示消息:

Inside SpellChecker constructor.
Inside TextEditor constructor.
Inside checkSpelling.

构造函数参数解析:

注释:上面这个例子里,将依赖类 SpellChecker.java注入到TextEditor.java 文件。

如此,便称为依赖注入。

如果存在不止一个参数时,当把参数传递给构造函数时,可能会存在歧义。要解决这个问题,那么构造函数的参数在 bean 定义中的顺序就是把这些参数提供给适当的构造函数的顺序就可以了。

考虑下面的类:

package x.y;
public class Foo {public Foo(Bar bar, Baz baz) {// ...}
}

下述配置文件工作顺利:

<beans><bean id="foo" class="x.y.Foo"><constructor-arg ref="bar"/><constructor-arg ref="baz"/></bean><bean id="bar" class="x.y.Bar"/><bean id="baz" class="x.y.Baz"/>
</beans>

让我们再检查一下我们传递给构造函数不同类型的位置。考虑下面的类:

package x.y;
public class Foo {public Foo(int year, String name) {// ...}
}

如果你使用 type 属性显式的指定了构造函数参数的类型,容器也可以使用与简单类型匹配的类型。例如:

<beans><bean id="exampleBean" class="examples.ExampleBean"><constructor-arg type="int" value="2001"/><constructor-arg type="java.lang.String" value="Zara"/></bean></beans>

最后并且也是最好的传递构造函数参数的方式,使用 index 属性来显式的指定构造函数参数的索引。下面是基于索引为 0 的例子,如下所示:

<beans><bean id="exampleBean" class="examples.ExampleBean"><constructor-arg index="0" value="2001"/><constructor-arg index="1" value="Zara"/></bean></beans>

最后,如果你想要向一个对象传递一个引用,你需要使用 标签的 ref 属性,如果你想要直接传递值,那么你应该使用如上所示的 value 属性。


2 基于setter的依赖注入

public class UserServiceImpl implents UserService{private UserDao userDao;@Autowirepublic serUserDao(UserDao userDao){this.userDao = userDao;}}

注:在 Spring 4.3 及以后的版本中,setter 上面的 @Autowired 注解是可以不写的。

3 基于字段(field)的依赖注入

public class UserServiceImpl implents UserService{@Autowireprivate UserDao userDao;}

基于字段(field) 的依赖注入优点

正如你所见,这种方式非常的简洁,代码看起来很简单,通俗易懂。你的类可以专注于业务而不被依赖注入所污染。你只需要把@Autowired扔到变量之上就好了,不需要特殊的构造器或者set方法,依赖注入容器会提供你所需的依赖。

基于字段(field )的依赖注入缺点

对于有final修饰的变量不好使

Spring的IOC对待属性的注入使用的是set形式,但是final类型的变量在调用class的构造函数的这个过程当中就得初始化完成,这个是基于字段的依赖注入做不到的地方.只能使用基于构造函数的依赖注入的方式

掩盖单一职责的设计思想

我们都知道在OOP的设计当中有一个单一职责思想,如果你采用的是基于构造函数的依赖注入的方式来使用Spring的IOC的时候,当你注入的太多的时候,这个构造方法的参数就会很庞大,类似于下面.

当你看到这个类的构造方法那么多参数的时候,你自然而然的会想一下:这个类是不是违反了单一职责思想?.但是使用基于字段的依赖注入不会让你察觉,你会很沉浸在@Autowire当中

public class VerifyServiceImpl implents VerifyService{private AccountService accountService;private UserService userService;private IDService idService;private RoleService roleService;private PermissionService permissionService;private EnterpriseService enterpriseService;private EmployeeService employService;private TaskService taskService;private RedisService redisService;private MQService mqService;public SystemLogDto(AccountService accountService, UserService userService, IDService idService, RoleService roleService, PermissionService permissionService, EnterpriseService enterpriseService, EmployeeService employService, TaskService taskService, RedisService redisService, MQService mqService) {this.accountService = accountService;this.userService = userService;this.idService = idService;this.roleService = roleService;this.permissionService = permissionService;this.enterpriseService = enterpriseService;this.employService = employService;this.taskService = taskService;this.redisService = redisService;this.mqService = mqService;}
}

与Spring的IOC机制紧密耦合

当你使用基于字段的依赖注入方式的时候,确实可以省略构造方法和setter这些个模板类型的方法,但是,你把控制权全给Spring的IOC了,别的类想重新设置下你的某个注入属性,没法处理(当然反射可以做到).

本身Spring的目的就是解藕和依赖反转,结果通过再次与类注入器(在本例中为Spring)耦合,失去了通过自动装配类字段而实现的对类的解耦,从而使类在Spring容器之外无效.

隐藏依赖性

当你使用Spring的IOC的时候,被注入的类应当使用一些public类型(构造方法,和setter类型方法)的方法来向外界表达:我需要什么依赖.但是基于字段的依赖注入的方式,基本都是private形式的,private把属性都给封印到class当中了.

无法对注入的属性进行安检

基于字段的依赖注入方式,你在程序启动的时候无法拿到这个类,只有在真正的业务使用的时候才会拿到,一般情况下,这个注入的都是非null的,万一要是null怎么办,在业务处理的时候错误才爆出来,时间有点晚了,如果在启动的时候就暴露出来,那么bug就可以很快得到修复(当然你可以加注解校验).

如果你想在属性注入的时候,想根据这个注入的对象操作点东西,你无法办到.我碰到过的例子:一些配置信息啊,有些人总是会配错误,等到了自己测试业务阶段才知道配错了,例如线程初始个数不小心配置成了3000,机器真的是狂叫啊!这个时候就需要再某些Value注入的时候做一个检测机制.

成也萧何败也萧何

基于 field 注入虽然简单,但是却会引发很多的问题。这些问题在我平常开发阅读项目代码的时候就经常遇见。

  • 容易违背了单一职责原则 使用这种基于 field 注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,普通的开发者很可能会无意识地给一个类添加很多的依赖。但是当使用构造器方式注入,到了某个特定的点,构造器中的参数变得太多以至于很明显地发现something is wrong。拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则(SRP:Single responsibility principle)。

    这个问题在我司的项目代码真的很常见。

  • 依赖注入与容器本身耦合

    依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖。换句话说,这个类应该是一个简单的POJO(Plain Ordinary Java Object)能够被单独实例化并且你也能为它提供它所需的依赖。

    这个问题具体可以表现在:

  • 你的类和依赖容器强耦合,不能在容器外使用

  • 你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化,这更像是集成测试

  • 不能使用属性注入的方式构建不可变对象(final 修饰的变量)

Spring 开发团队的建议

Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.

通过上面,我们可以看到,基于字段的依赖注入方式有很多缺点,我们应当避免使用基于字段的依赖注入.推荐的方法是使用基于构造函数基于setter的依赖注入.对于必需的依赖项,建议使用基于构造函数的注入,以使它们成为不可变的,并防止它们为null。对于可选的依赖项,建议使用基于Setter的注入

简单来说,就是

  • 强制依赖就用构造器方式

  • 可选、可变的依赖就用setter 注入

    当然你可以在同一个类中使用这两种方法。构造器注入更适合强制性的注入旨在不变性,Setter注入更适合可变性的注入。

让我们看看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. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任

而对于基于 setter 的注入,他们是这么说的:

Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.

基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入

4 Spring 支持使用@Autowired@Resource,  @Inject 三个注解进行依赖注入

1 @Autowired

@Autowired为Spring 框架提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired

public interface Svc {void sayHello();
}@Service
public class SvcA implements Svc {@Overridepublic void sayHello() {System.out.println("hello, this is service A");}}@Service
public class SvcB implements Svc {@Overridepublic void sayHello() {System.out.println("hello, this is service B");}}@Service
public class SvcC implements Svc {@Overridepublic void sayHello() {System.out.println("hello, this is service C");}
}

测试类:

@SpringBootTest
public class SimpleTest {@Autowired// @Qualifier("svcA")Svc svc;@Testvoid rc() {Assertions.assertNotNull(svc);svc.sayHello();}}

装配顺序:

  1. 按照type在上下文中查找匹配的bean

    • 查找type为Svc的bean 复制代码

  2. 如果有多个bean,则按照name进行匹配

    • 查找name为svc的bean 复制代码

    • 查找name为svcA的bean 复制代码

    1. 如果有@Qualifier注解,则按照@Qualifier指定的name进行匹配

    2. 如果没有,则按照变量名进行匹配

  3. 匹配不到,则报错。(@Autowired(required=false),如果设置requiredfalse(默认为true),则注入失败时不会抛出异常)

2 @Inject

在Spring 的环境下,@Inject@Autowired 是相同的,因为它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor来处理的。

3 @Resource

@Resource是JSR-250定义的注解。Spring 在 CommonAnnotationBeanPostProcessor实现了对JSR-250的注解的处理,其中就包括@Resource

@Resource有两个重要的属性:nametype,而Spring 将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。

装配顺序:

  1. 如果同时指定了nametype,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。

  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。

  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。

  4. 如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。

IDEA 提示 Field injection is not recommended

在使用IDEA 进行Spring 开发的时候,当你在字段上面使用@Autowired注解的时候,你会发现IDEA 会有警告提示:

Field injection is not recommended

Inspection info: Spring Team Recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies".

翻译过来就是这个意思:

不建议使用基于 field 的注入方式。

Spring 开发团队建议:在你的Spring Bean 永远使用基于constructor 的方式进行依赖注入。对于必须的依赖,永远使用断言来确认。

比如如下代码:

@Service
public class HelpService {@Autowired@Qualifier("svcB")private Svc svc;public void sayHello() {svc.sayHello();}
}public interface Svc {void sayHello();
}@Service
public class SvcB implements Svc {@Overridepublic void sayHello() {System.out.println("hello, this is service B");}
}

将光标放到@Autowired处,使用Alt + Enter 快捷进行修改之后,代码就会变成基于Constructor的注入方式,修改之后

@Service
public class HelpService {private final Svc svc;@Autowiredpublic HelpService(@Qualifier("svcB") Svc svc) {// Assert.notNull(svc, "svc must not be null");this.svc = svc;}public void sayHello() {svc.sayHello();}
}

如果按照Spring 团队的建议,如果svc是必须的依赖,应该使用Assert.notNull(svc, "svc must not be null")来确认。

修正这个警告提示固然简单,但是我觉得更重要是去理解为什么Spring 团队会提出这样的建议?直接使用这种基于 field 的注入方式有什么问题?

22 springboot依赖注入三种方式相关推荐

  1. Spring学习3 - Bean的属性、DI依赖注入三种方式

    文章目录 1. 属性设置 - XML配置文件.以及与其等价的注解形式 2. 依赖注入DI的方式 2.1 属性自动注入 - 不推荐 代码准备阶段 代码测试 2.2 子节点构造器注入 - 不推荐 2.3 ...

  2. Spring 依赖注入三种方式的实现,及循环依赖问题的解决(源码+XML配置)

    搬砖啦,搬砖啦,这几天在看Spring相关的书,下面给大家分享一下这几天的心得与收获,Go Go Go! Spring支持两种依赖注入方式,分别是属性注入,构造函数注入.除此之外,Spring还支持工 ...

  3. spring依赖注入三种方式

    首先,要学习Spring中的Bean的注入方式,就要先了解什么是依赖注入. 依赖注入是指:让调用类对某一接口的实现类的依赖关系由第三方注入,以此来消除调用类对某一接口实现类的依赖. Spring容器中 ...

  4. Spring循环依赖的三种方式以及解决办法

    Spring循环依赖的三种方式以及解决办法 [转]https://www.cnblogs.com/liuqing576598117/p/11227007.html 示例 https://github. ...

  5. go引入外部依赖的三种方式:go get,go module,vendor目录

    本文转载自golang 引入外部包的三种方式:go get, go module, vendor目录_guoguolifang的博客-CSDN博客_golang 外部包 import  "g ...

  6. 面试必问:Spring循环依赖的三种方式

    作者:学习园 链接:https://blog.csdn.net/u010644448 引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时 ...

  7. Spring循环依赖的三种方式,你都清楚吗?

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 来源:22j.co/bUdX 引言:循环依赖就是N个类中循环嵌套引用,如果 ...

  8. Spring循环依赖的三种方式

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种: ...

  9. spring循环依赖及解决方式_来探究一下Spring 循环依赖的三种方式

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种: ...

最新文章

  1. 使用hql动态创建对象问题
  2. MySQL笔记11:C语言编程API函数和完整实例演示!
  3. NYOJ 311 完全背包
  4. 维护IBM DB2数据库所应领会的基本常识-8
  5. zigbee无线通信数码管实验、usbDongle抓包、发字符串
  6. python里删除range里的数字_python中range函数与列表中删除元素
  7. Python OpenCV显示图像并保存图像
  8. 分布式数据库的字符集
  9. Spring学习之路——简单入门HelloWorld
  10. 最简单的三步yum配置阿里源
  11. python 读取PDF内容(推荐pdfplumber)
  12. 猿人时代java_猿人时代攻略
  13. 在Unity中创建基于Node节点的编辑器 (二) 窗口序列化
  14. Robocup新手指南
  15. SVM学习笔记-对偶形式的SVM
  16. 保留两位小数的四舍五入
  17. 基于 C# 和 js 开发的程序员升职记 remake 版
  18. 迅驰时尚盛典,果然不错!
  19. Element-UI可编辑表格的实现
  20. 无线信号的传输与接收(Channel-arrangement)

热门文章

  1. Appium图像识别技术OpenCV
  2. Study English Easy Way!!
  3. 维吉尼亚密码 php,python小脚本之维吉尼亚密码
  4. 视频号冠名崔健演唱会太野了4600万观看
  5. Gen8 ESXi 6.5 硬盘RDM 直通
  6. 看OpenFace如何做到精准人脸识别
  7. Ubuntu 18.04 安装 OpenFace
  8. readdir不保证读取的文件顺序
  9. TortoiseGit
  10. android apdp分区,EXE版 KDZTools (无需安装Python)