Spring依赖注入的三种方式(好的 坏的和丑的)
关于spring bean三种注入方式的优缺点对比,翻译自Spring DI Patterns: The Good, The Bad, and The Ugly,水平有限,如有错误请指正。
Spring开发者会很熟悉spring强大的依赖注入API,这些API可以让你用@Bean的注解让Spring实例化和管理Bean。Bean之间的任何依赖都会被spring解析和注入。
三种依赖于注解的注入方法
spring有三种注解的方式让你来声明类的依赖。
- 字段注入(坏的)
import org.springframework.beans.factory.annotation.Autowired;
public class MyBean {@Autowiredprivate AnotherBean anotherBean;//Business logic...
}
- 设值注入(丑的)
import org.springframework.beans.factory.annotation.Autowired;
public class MyBean {private AnotherBean anotherBean;@Autowiredpublic void setAnotherBean(final AnotherBean anotherBean) {this.anotherBean = anotherBean;}//Business logic...
}
- 构造器注入(好的)
public class MyBean {private final AnotherBean anotherBean;public MyBean(final AnotherBean anotherBean) {this.anotherBean = anotherBean;}//Business logic...
}
字段注入难以忽视的真相
这几种方式中最常用的就是字段注入,很有可能是因为这是最方便的方式。不幸的是,因为它的普遍性,开发者很少了解到其他两种方式相互之间的优缺点。
使用字段注入的类会变得越来越难以维护
当你用的字段注入模式,并且想在类里增加依赖时,你只需要加一个字段,然后加上@Autowired或者@Inject注解,然后就可以走了。听起来很棒,但几个月以后,你的类就会变成只有上帝才能理清楚的类了。 当然,这也很可能发生在另外两中方式上,但是另两种方式能迫使你更关注类中的依赖关系。
只要你用了字段注入,单测就没法做了
当我看了Josh Long关于Spring boot的演讲后,这句话就一直萦绕在我的脑海里, 从某种意义上来说,它也促使我写下这篇文章。你怎么测试字段注入的类?很有可能你正在回想那些不太直观的 Mockito 用法,就像这样。
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class MyBeanTest {@Mockprivate AnotherBean anotherBean;@InjectMocksprivate MyBean target;//Tests...
}
这种利用反射的方式迫使开发者需要关注很多其他的地方,比如
- 如果MyBean有多个其他依赖怎么办?
- 我是否应该创建一个target实例,或者只是声明它?有什么不同?
- 当依赖用到泛型的时候你是否能保证类型安全?
- 如果你只需要部分依赖的真实实现怎么办?
用了字段注入的类都是非final的,容易产生循环依赖
如果是你想把@Autowired自动注入的字段声明为final类型的,编译器会直接报错,是不是很烦人。 而且这个字段只能被设置一次。除非你加了@Lazy注解,否则spring会在启动的时候去解析依赖图,你的bean可能因为循环依赖报出一个BeanCurrentlyInCreationException,例如:
public class A {@Autowiredprivate B b;
}
public class B {@Autowiredprivate C c;
}
public class C {@Autowiredprivate A a;
}
现实中肯定不会出现这么简单的错误,但实际中可能会出现很多因为继承、跨类库,跨架构导致的依赖迷宫。这个问题可以提供把其中某个字段声明为非必须(可以通过@Autowired(required = false)允许为空),或者使用懒加载(使用@Lazy可以再解析完bean之后再设值)。遇到过这个Exception的人都知道,找到循环依赖中确切的一环是非常耗时耗力的工作。一旦你找到了,你如何确定牺牲那个依赖呢?你怎么恰当的把这些写到文档里呢?
spring中有很多种解决循环依赖的方法,而且现在有些方法开始变的很恶心了。
优点
- 最简洁
- 很多java开发者都喜欢这种方式
缺点
- 便利会弱化代码结构设计
- 很难测试
- 依赖不能是可变的(无法final)
- 容易出现循环依赖
- 需要使用到多个spring或者java注解
设值注入
模板和封装
三种方式里,设值注入是最模板化的,每个bean都必须有有个setter函数,每个setter函数必须加@Autowired或@Inject注解。这种方式你不用考虑你类依赖的数量问题,这算是另一种设计方式。 但你过多暴露类的内部,违反了开放封闭原则。
设值注入让单测变的简单
不需要反射的黑魔法,你只需要把你的依赖set进去。
import org.junit.Before;
import org.mockito.Mockito;
public class MyBeanTest {private MyBean target = new MyBean();private AnotherBean anotherBean = Mockito.mock(AnotherBean.class);@Beforepublic void setUp() {myBean.setAnotherBean(anotherBean);}//Tests...
}
设值注入对循环依赖免疫
使用设值注入,spring不会对你的bean做有向无环图依赖分析,这就意味着可以有循环依赖。允许循环依赖是把双刃剑,你不必处理那些因为循环依赖导致的恶心的问题,但你的代码以后也就很难分解开了。 试试上BeanCurrentlyInCreationException只是在启动时告诉你你的设计有问题。
优点
- 对循环依赖免疫
- 随着setter的添加,高度耦合的类很容易被识别出来。
缺点
- 违反开放封闭原则
- 会把循环依赖隐藏掉
- 三种方法里最模板化的方式
- 依赖不能是可变的(无法final)
终结方案:构造器注入
事实证明构造器注入是最佳的依赖注入解决方案。一些新的支持持续集成的平台,比如Angular,已经从其他平台吸取了教训,只支持构造器注入。
构造器注入能暴露出过度耦合的问题
无论什么时候你的类需要一个新的依赖,你都得加一个构造参数,这就会强迫你去审视你类的耦合度。我发现少于3个依赖是比较好的,如果多于5个依赖,就应该重构了。只在短短几行连续的代码上数有多少个依赖是很容易的。
额外的好处是,由于final字段可以在构造函数中初始化,所以我们的依赖关系可以是final的。恩,就应该是这样!
测试注入的构造函数类很简单
甚至比设值注入更简单。
import org.mockito.Mockito;
public class MyBeanTest {private AnotherBean anotherBean = Mockito.mock(AnotherBean.class);private MyBean target = new MyBean(anotherBean);//Tests...
}
注入子类的构造函数必须具有非默认构造函数
使用构造函数注入的类的任何子类都必须具有调用父构造函数的构造函数。如果您继承了Spring组件,这就很麻烦了。我个人很少碰到这种情况。我尽量避免在父组件中注入依赖——我通常是通过组合而不是继承完成的。
优点
- 依赖可以是final的
- spring官方推荐的方式
- 三种方式里最容易测试的方式
- 高耦合类随着构造参数的增长很容易被识别出来
- 其他开发平台的开发者也很熟悉
- 不需要依赖@Autowired注解
缺点
- 构造函数需要下沉到子类
- 容易产生循环依赖
结论
构造器注入用起来吧
有时候其他模式也有意义,但“为了与代码库的其余部分保持一致”和“使用字段注入模式更简单”并不是有效的借口。
例如,使用设值注入模式从xml setter注入方式迁移,或者需要修复BeanCurrentlyInCreationException问题时的中间状态,但并不意味着你最终就应该是这样。
甚至字段注入模式也足够了,例如,设计解决方案或回答StackOverflow上的问题时,除非他们的问题是关于Java中的依赖注入。在这种情况下,您应该用字段注入方便说明问题。
Spring依赖注入的三种方式(好的 坏的和丑的)相关推荐
- spring依赖注入的三种方式以及优缺点
spring依赖注入的三种方式以及优缺点 一.依赖注入的三种方式 1.通过构造器注入.(spring4.3之后,推荐使用) 2.通过setter注入.(spring4.3之前,推荐使用) 3通过fil ...
- spring 依赖注入的三种方式
@Autowired:构造器,参数,方法,属性:都是从容器中获取参数组件的值: set方法注入: 构造器注入: 作为Bean方法的参数注入: 例子:Boss 注入Car类 第一种.set方式注入 // ...
- Spring属性注入的三种方式(超详细)
属性注入的三种方式 使用set方法进行注入 使用有参构造函数进行注入 使用p名称空间注入 首先了解下面两个名词的含义: IOC:控制反转(Inversion of Control,缩写为IoC),是面 ...
- 05.bean依赖注入的三种方式
05.bean依赖注入的三种方式 1.概述 依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现. 在编写程序时,通过控制反转,把对象的创建交给 ...
- spring依赖注入的4种方式
Spring 依赖注入的4种方式 一.Set注入 必须要有setter方法 public class UserDao {public void test(){System.out.println(&q ...
- php依赖注入的三种方式
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度.其中最常见的方式叫做依赖注入(Dependency Inject ...
- Spring系列之依赖注入的三种方式
目录 一.依赖注入方式 1.使用属性的setXXX方法注入 2.构造函数注入 (1)按类型匹配入参type (2)按索引匹配入参index (3)联合使用类型和索引匹配入参[type和index一起使 ...
- Spring注解依赖注入的三种方式的优缺点以及优先选择
当我们在使用依赖注入的时候,通常有三种方式: 1.通过构造器来注入: 2.通过setter方法来注入: 3.通过filed变量来注入: 那么他们有什么区别吗?应该选择哪种方式更好? 代码示例: Con ...
- 依赖注入的三种方式_一起学Spring之三种注入方式及集合类型注入
本文主要讲解Spring开发中三种不同的注入方式,以及集合数据类型的注入,仅供学习分享使用,如有不足之处,还请指正. 概述 Spring的注入方式一共有三种,如下所示: 通过set属性进行注入,即通过 ...
最新文章
- eBPF Tracing 入门教程与实例
- 36条网络安全术语盘点——Vecloud
- 深度学习(DL)与卷积神经网络(CNN)学习笔记随笔-02-基于Python的卷积运算
- SQL Server 2014 Win7 Win10 安装详解 SQL Server 2017 2019 Linux及SQL TSQL ETL实用案例
- 使用Java EE和OIDC构建Java REST API
- JeecgBoot 单体升级微服务快速方案(十分钟搞定)
- 36 万美元套利!3 步骤揭秘黑客 DeFi 闪电贷全过程
- 论剑乌镇:历届互联网大会热词盘点
- 关键词提取_tf_idf
- 位图(bitmap)—— C语言实现
- python安卓下载-python手册中文版apk下载
- bzoj 1064: [Noi2008]假面舞会(DFS)
- RPM打包原理、示例、详解及备查( 转)
- 页面导出excel的三种方式
- 用大O记号法测量算法的效率(Algorithm efficiency Asymptotic notation Big O notation)
- 电脑蓝屏c语言代码大全,电脑蓝屏代码C0000218怎么解决方法
- 7 Android的VM虚拟机是哪个,Android的VM虚拟机是哪个?
- 重置计算机网络配置后上不了网,win10系统网络重置后不能连接网络如何解决
- 从纯洁男孩到堕落男人
- [人工智能-深度学习-39]:环境搭建 - 训练主机硬件选择全指南(CPU/GPU/内存/硬盘/电源)
热门文章
- MGRE实验配置(华为)
- 如何把App放在服务器上供用户下载
- Hard Disk Sentinel 3.7 硬盘哨兵 汉化
- IPFS安装及初始化
- java oval xml 校验
- 人工智能 漆桂林_领域专家走进平安科技(PA Tech),共议知识图谱为医疗AI赋能路径...
- Linux次级代谢物分析,9大验证,精准靶向代谢组学技术对植物各部分次级代谢产物分析研究...
- Linux 性能优化实战(倪朋飞)---CPU 使用率
- nginx:[error] invalid PID number ““ in “xxx/xxx/nginx/nginx.pid“ 问题解决方案
- python撤回快捷键大全_PythonIDEPyCharm的快捷键大全