Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java系列 | 属性依赖注入被认为是有害的

Java系列 | 属性依赖注入被认为是有害的

作者头像
Tinywan
发布于 2023-03-08 12:20:40
发布于 2023-03-08 12:20:40
79700
代码可运行
举报
文章被收录于专栏:开源技术小栈开源技术小栈
运行总次数:0
代码可运行

简介

在依赖注入框架中,字段注入是一种非常流行的做法,例如Spring。然而,它有几个严重的权衡因素,一般来说应该避免。

注入类型

有三种主要方式可以将你的依赖注入到你的类中。构造器、设置器(方法)和字段注入。让我们快速比较一下用所有方法注入的相同依赖的代码。

构造函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;

@Autowired
public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) {
    this.dependencyA = dependencyA;
    this.dependencyB = dependencyB;
    this.dependencyC = dependencyC;
}

Setter注入

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;

@Autowired
public void setDependencyA(DependencyA dependencyA) {
    this.dependencyA = dependencyA;
}

@Autowired
public void setDependencyB(DependencyB dependencyB) {
    this.dependencyB = dependencyB;
}

@Autowired
public void setDependencyC(DependencyC dependencyC) {
    this.dependencyC = dependencyC;
}

Field 属性注入

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Autowired
private DependencyA dependencyA;

@Autowired
private DependencyB dependencyB;

@Autowired
private DependencyC dependencyC;

有什么问题吗?

正如你所看到的,Field变量看起来非常漂亮。它很短,很简洁,没有模板代码。这段代码很容易阅读和浏览。

你的类可以只关注重要的东西,而不被DI的模板所污染。你只需在字段上方加上@Autowired注解,就可以了。没有特殊的构造函数或设置函数,只是为了让DI容器提供你的依赖性。Java是非常冗长的,所以每一个能让你的代码变短的机会都是值得欢迎的,对吗?

违反单一责任原则

添加新的依赖关系是非常容易的。也许太容易了。增加六个、十个甚至十几个依赖关系都没有问题。当你使用构造函数进行DI时,到了一定程度后,构造函数参数的数量变得太多,就会立刻发现有问题。

有太多的依赖关系通常意味着这个类有太多的责任。这可能是对单一责任原则和关注点分离的违反,是一个很好的指标,说明该类需要进一步检查并可能进行重构。当直接注入字段时,没有这样的红旗,因为这种方法可以无限地扩展。

依赖性隐藏

使用DI容器意味着类不再负责管理它自己的依赖关系。获取依赖关系的责任从类中提取。其他的人现在负责提供依赖--DI 容器或在测试中手动分配它们。

当类不再负责获取它的依赖关系时,它应该使用公共接口--方法或构造函数来清楚地传达它们。这样就可以清楚地知道该类需要什么,以及它是可选的(设置器)还是必须的(构造器)。

DI容器耦合

DI框架的核心思想之一是管理类不应该依赖所使用的DI容器。换句话说,它应该只是一个普通的POJO,可以独立地被实例化,只要你把所有需要的依赖传递给它。

这样你就可以在单元测试中实例化它,而不启动DI容器,并单独测试它(用一个容器,这将是更多的集成测试)。

如果没有容器耦合,你可以将该类作为托管或非托管类使用,甚至可以切换到一个新的DI框架。

然而,当直接注入字段时,你没有提供直接的方式来实例化该类及其所有需要的依赖性。这意味着。

  • 有一种方法(通过调用默认构造函数)可以在一个状态下使用new创建一个对象,当它缺乏一些必须的合作者时,使用会导致NullPointerException
  • 这样的类不能在DI容器(测试、其他模块)之外被重用,因为除了反射之外没有办法为它提供所需的依赖。

不变性

与构造函数不同,字段注入不能用于将依赖关系分配给最终字段,从而有效地使你的对象变得易变。

构造函数与设置函数的注入

所以字段注入可能不是办法。剩下的是什么?Setters设置器和构造器。哪一个应该被使用?

Setters设置器

设置器应该被用来注入可选的依赖关系。当它们没有被提供时,该类应该能够发挥作用。在对象被实例化后,可以随时改变依赖关系。这可能是也可能不是一个优势,取决于具体情况。

有时,拥有一个不可变的对象是可取的。有时,在运行时改变对象的合作者是很好的--比如JMX管理的MBeans

Spring 3.x文档的官方建议是鼓励使用设置器而不是构造器。

Spring团队通常提倡设置器注入,因为大量的构造器参数会变得不方便,特别是当属性是可选的时候。设置器方法也使该类的对象可以在以后进行重新配置或重新注入。通过JMX MBeans进行管理是一个引人注目的用例。

一些纯粹主义者赞成基于构造器的注入。提供所有对象的依赖性意味着对象总是以完全初始化的状态返回给客户端(调用)代码。其缺点是,对象变得不容易被重新配置和重新注入。

构造函数

构造函数注入适用于强制性的依赖关系。这些是对象正常运行所需要的。通过在构造函数中提供这些字段,你可以确保对象在被构造的那一刻就可以被使用。在构造函数中分配的字段也可以是最终的,允许对象是完全不可变的,或者至少是保护它所需的字段。

使用构造函数来提供依赖关系的一个结果是,以这种方式构造的两个对象之间的循环依赖关系不再可能(与setter注入不同)。这实际上是一件好事,而不是限制,因为循环依赖应该被避免,而且通常是一个糟糕设计的标志。这种方式可以防止这种做法。

另一个好处是,如果使用spring 4.3+,你可以将你的类与DI框架完全解耦。原因是Spring现在支持隐式构造函数注入一个构造函数的场景。这意味着你不再需要在你的类中进行DI注释。当然,你也可以通过在你的Spring配置中为给定的类显式配置DI来实现同样的效果,这只是让这一切变得更容易。

从Spring 4.x开始,Spring文档的官方建议发生了变化,setter 注入的官方建议不再鼓励构造函数:

Spring团队通常提倡构造函数注入,因为它使人们能够将应用组件实现为不可变的对象,并确保所需的依赖关系不为空。此外,注入构造函数的组件总是以完全初始化的状态返回给客户端(调用)代码。 顺便提一下,大量的构造函数参数是一种不好的代码气味,意味着该类可能有太多的责任,应该重构以更好地解决适当的分离问题。

设置器注入主要应该只用于在类中可以分配合理默认值的可选依赖。否则,必须在代码使用该依赖关系的所有地方进行非空值检查。 设置器注入的一个好处是,设置器方法使得该类的对象可以在以后进行重新配置或重新注入。

更新:IntelliJ IDEA支持

自从这篇文章发表后,IDEA引入了一些贴心的支持,用于检测和轻松修复字段注入。

它可以自动从字段中移除@Autowired注解,而创建一个具有@Autowired依赖性的构造函数,有效地用构造函数注入取代了字段注入。

结论

大部分情况下应该避免字段注入。作为替代,你应该使用构造函数或方法来注入你的依赖关系。

两者都有其优点和缺点,使用方法取决于情况。然而,由于这些方法可以混合使用,所以这不是一个非此即彼的选择,你可以在一个类中结合使用setter和constructor注入。

构造函数更适合于强制性的依赖关系和追求不变性的情况。 设置器则更适合于可选的依赖关系。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
@Autowired依赖注入为啥不推荐了
这几天更新升级了一下java编码神器IDEA,升级完进行日常开发,可能是以前用的IDEA版本比较老旧,升级之后发现之前的日常写法有了个warning提醒。来看图:
botkenni
2022/09/07
1.6K0
@Autowired依赖注入为啥不推荐了
大公司为什么禁止在 Spring Boot 项目中使用 @Autowired 注解?
查阅了相关文档了解了一下,原来这个提示是spring framerwork 4.0以后开始出现的,spring 4.0开始就不推荐使用属性注入,改为推荐构造器注入和setter注入。
搜云库技术团队
2023/08/08
5540
大公司为什么禁止在 Spring Boot 项目中使用 @Autowired 注解?
Spring Framework中的依赖注入:构造器注入 vs. Setter注入
构造器注入和Setter注入是依赖注入(Dependency Injection,DI)中两种常见的方式,用于向一个对象注入其所依赖的其他对象或数值。这两种注入方式有各自的特点和用途。
关忆北.
2023/11/15
6950
Spring6框架中依赖注入的多种方式(推荐构造器注入)
一个典型的企业应用程序不是由单个对象(或在Spring术语中称为bean)组成的。
codetrend
2024/05/26
4120
Spring 学习笔记(四)—— XML配置依赖注入
  依赖注入(DI)与控制反转(IoC)是同一个概念,都是为了处理对象间的依赖关系。
Rekent
2018/09/04
4070
深入理解 Spring IoC 和 DI:掌握控制反转和依赖注入的精髓
控制反转是软件工程中的一个原则,它将对象或程序的某些部分的控制权转移给容器或框架。我们最常在面向对象编程的上下文中使用它。
小万哥
2023/08/11
6790
深入理解 Spring IoC 和 DI:掌握控制反转和依赖注入的精髓
Spring依赖注入的三种方式(好的 坏的和丑的)
  关于spring bean三种注入方式的优缺点对比,翻译自Spring DI Patterns: The Good, The Bad, and The Ugly,水平有限,如有错误请指正。   Spring开发者会很熟悉spring强大的依赖注入API,这些API可以让你用@Bean的注解让Spring实例化和管理Bean。Bean之间的任何依赖都会被spring解析和注入。
xindoo
2021/01/22
1.9K0
【Spring】浅谈spring为什么推荐使用构造器注入
一、前言 ​ Spring框架对Java开发的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反转)和AOP,平时使用最多的就是其中的IOC,我们通过将组件交由Spring的IOC容器管理,将对象的依赖关系由Spring控制,避免硬编码所造成的过度程序耦合。前几天的时候,笔者的同事问我为什么要使用构造器的注入方式,我回答说因为Spring文档推荐这种,而说不出为什么 T^T,后面抽时间了解了一下,下面就是笔者要讨论的就是其注入方式。 二、常见的三种注入方式 ​ 笔
joemsu
2018/05/17
2K0
Spring干货集|Bean依赖你又觉得行了?
实际的系统几乎不可能仅有单一的bean,都是很多个bean协作提供服务。本文目标也就是讨论如何冲破单一 bean 定义而让多 bean 协作实现系统。
JavaEdge
2021/02/23
8510
Spring干货集|Bean依赖你又觉得行了?
Spring框架入门(三):依赖注入和控制反转
依赖注入(IoC) 和 控制反转(DI) 有什么关系呢?其实它们是同一个概念的不同角度描述。
sr
2019/05/23
5870
【源码分析】Spring依赖注入原理
在Spring中提供了三种实现依赖注入的方式:字段注入、构造器注入、Setter方法注入。
百思不得小赵
2022/12/01
7750
【源码分析】Spring依赖注入原理
Spring系列之依赖注入的方式「建议收藏」
      对于spring配置一个bean时,如果需要给该bean提供一些初始化参数,则需要通过依赖注入方式,所谓的依赖注入就是通过spring将bean所需要的一些参数传递到bean实例对象的过程,spring的依赖注入有3种方式:
Java架构师必看
2022/02/03
3.4K0
你可能使用了Spring最不推荐的注解方式
使用Spring框架最核心的两个功能就是IOC和AOP。IOC也就是控制反转,我们将类的实例化、依赖关系等都交由Spring来处理,以达到解耦合、利用复用、利于测试、设计出更优良程序的目的。而对用户来说,操作最对的便是注解。在Spring中提供了三类注解方式,下面我们就逐一分析。最后,你会发现,你最常用、看起来最方便的形式确实最不推荐的一种形式。
程序新视界
2022/05/09
2540
面试题:说说你对依赖注入的理解?
依赖注入(Dependency Injection,DI),是一种对象创建与组装的技术,它通过将对象所依赖的其他对象的引用(或实例)传递给其构造函数、属性或者方法等形式来实现对象之间的解耦。这种解耦可以帮助我们更好地管理对象之间的依赖关系,并提高代码的可重用性和可维护性。
GeekLiHua
2025/01/21
630
Spring为什么建议构造器注入?
本章的内容主要是想探讨我们在进行Spring 开发过程当中,关于依赖注入的几个知识点。感兴趣的读者可以先看下以下问题:
Java旅途
2021/07/13
1.7K0
PHP系列 | 依赖注入容器和服务定位器
依赖注入(Dependency Injection,DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接口名或一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。
Tinywan
2019/08/20
1.1K0
ASP.NET Core 依赖注入(DI)简介
本文为官方文档译文 ASP.NET Core是从根本上设计来支持和利用依赖注入。 ASP.NET Core应用程序可以通过将其注入到Startup类中的方法中来利用内置的框架服务,并且应用程序服务也可以配置为注入。 ASP.NET Core提供的默认服务容器提供了一个最小的功能集,而不是替换其他容器。 什么是依赖注入? 依赖注入,英文是Dependency Injection一般简称DI,是实现对象与其协作者或依赖关系之间松散耦合的技术。为了执行其操作,类所需的对象不是直接实例化协作者或使用静态引用,
晓晨
2018/06/22
3.2K0
ASP.NET Core中的依赖注入(2):依赖注入(DI)
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用“好莱坞原则”是应用程序以被动的方式实现对流程的定制。我们可以采用若干设计模式以不同的方式实现IoC,比如我们在上面介绍的模板方法、工厂方法和抽象工厂,接下来我们介绍一种更为有价值的IoC模式,即依赖注入(DI:Dependency Injection,以下简称DI)。 目录 一、由外部容器提供服务对象 二、三种依赖注入方式     构造器注入     属性注入     方法注入 三、实例演示:创建
蒋金楠
2018/01/15
2.3K0
ASP.NET Core中的依赖注入(2):依赖注入(DI)
编织Spring魔法:解读核心容器中的Beans机制【beans 一】
在现代软件开发领域,Spring框架已经成为构建强大、灵活、可维护应用程序的不二选择。而Spring的核心容器是这一框架的心脏,Beans则是其中最为关键的组成部分。本文将带您深入探索这个令人着迷的世界,解锁Beans的神秘面纱,让您更深刻地理解Spring的精髓。
一只牛博
2025/05/30
940
编织Spring魔法:解读核心容器中的Beans机制【beans 一】
依赖注入和循环依赖注入
1.java bean注入的两种方式 1.1 Resource注解方式 @Resource private NestedComponent nestedComponent2; 1.2 构造器注入的方式来 private NestedComponent nestedComponent1;
oktokeep
2024/11/21
2590
推荐阅读
相关推荐
@Autowired依赖注入为啥不推荐了
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验