【Spring】三大依赖注入(@Autowired,Setter,构造方法)

news2024/9/19 8:45:47

目录

一、属性注入(@Autowired)

1.1 优点分析

1.2 缺点分析

1.2.1 无法实现final修饰的变量注入。

1.2.2 兼容性不好

1.2.3 (可能违背)设计原则问题

1.2.4 代码举例:

1.2.5 出现循环依赖该怎么办?

二、Setter注入

2.1 优点分析

2.2 缺点分析

2.2.1 不能注入不可变对象

2.2.2 注入对象可被修改

三、构造方法注入

3.1 优点分析

3.1.1 可注入不可变对象

3.1.2 注入对象不会被修改

3.1.3 注入对象会被完全初始化

3.1.4 通用性更好


一、属性注入(@Autowired)

属性注入是使用@Autowired实现的,如下:将UserService类注入到UserController类中:

启动类如下:

运行结果如下:

1.1 优点分析

属性注入的最大优点就是实现简单、使用简单。只需要给变量加上一个@Autowired,就可以在不new对象的情况下,直接获得注入的对象——这也正是DI的功能及其魅力所在。

1.2 缺点分析

1.2.1 无法实现final修饰的变量注入。

原因也很简单,在Java中final对象(不可变)要么直接赋值,要么在构造方法中赋值,所以当使用属性注入final对象时,它不符合Java中final的使用规范,所以也就无法注入成功。

问:那么如果要注入一个不可变的对象,该如何实现呢?

答;使用构造方法注入。

1.2.2 兼容性不好

只适用于IoC容器。如果将属性注入的代码移植到其他非 IoC 的框架中,那么代码就无效了,所以属性注入的通用性不是很好。

1.2.3 (可能违背)设计原则问题

属性注入容易违背单一设计原则是因为它会导致类的职责不够单一,在属性注入中,一个类可能会同时拥有多个依赖,这些依赖可能与该类的主要职责无关,导致类的职责不够单一,使代码变得难以维护。

而单一设计原则的核心思想是:一个类应该只有一个职责,只有这样才能使代码易于维护和扩展,因此,在使用属性注入时,特别需要注意类的职责是否清晰,以及注入的属性是否与类的主要职责相关。

当然,也不是说一定会出现违背单一原则的情况,但是不可否认的是:注入实现越简单,那么滥用它的概率也越大,所以出现违背单一职责原则的概率也越大。 注意:这里强调的是违背设计原则(单一职责)的可能性,而不是一定会违背设计原则,二者有着本质的区别。

1.2.4 代码举例:

当使用@Autowired注入时,一个类可能会依赖于多个其他类或接口,这样会导致这个类的职责过重,违反了单一设计原则。下面是一个例子:

假设有一个OrderService类,它需要依赖于一个UserService和一个ProductService来完成一些业务逻辑。如果使用@Autowired注入这两个依赖,那么OrderService将会依赖于UserService和ProductService两个类,导致OrderService职责过重。

@Service
public class OrderService {
 
    @Autowired
    private UserService userService;
 
    @Autowired
    private ProductService productService;
 
    public void placeOrder() {
        // use userService and productService to place order
    }
}

这里OrderService类的职责包含了用户管理和商品管理,这违反了单一设计原则。如果将UserService和ProductService作为方法参数传递,而不是使用@Autowired注入,那么OrderService就只需要关注订单管理相关的逻辑,而不需要依赖其他的类或接口,符合单一设计原则。

1.2.5 出现循环依赖该怎么办?

什么是循环依赖?

在Spring中,当一个bean被初始化时,如果依赖的bean还未初始化,Spring会把该bean放入一个专门用来存储正在初始化的bean的缓存中,以便在依赖的bean初始化完成后再将其注入。但如果两个bean相互依赖,那么它们的初始化顺序就会产生死锁,导致应用程序无法启动。

@Autowired注入可以让类和类之间的关系变得紧密,容易出现循环依赖和复杂的依赖关系,从而违反了单一设计原则中的“高内聚,低耦合”的原则。

以下是一个例子:

public class OrderService {
    @Autowired
    private UserService userService;
    
    public void createOrder() {
        // 创建订单代码
        User user = userService.getUser();
        // 订单相关代码
    }
}

public class UserService {
    @Autowired
    private OrderService orderService;
    
    public User getUser() {
        // 获取用户代码
        Order order = orderService.getOrder();
        // 用户相关代码
    }
}

在这个例子中,OrderService和UserService互相依赖,存在循环依赖关系(具体来说,当容器创建bean A时,会发现A需要依赖B,于是容器会先创建bean B,但是创建B又发现B需要依赖A,于是容器又会回去创建bean A,这样就形成了循环依赖的问题。)。这使得系统中类之间的依赖关系变得复杂,不利于代码的维护和扩展。

为了解决这个问题,可以采用构造函数注入来替代@Autowired注入。构造函数注入避免循环依赖问题,并且更容易维护和测试。例如,可以通过以下方式来进行改进:

public class OrderService {
    private UserService userService;
    
    public OrderService(UserService userService) {
        this.userService = userService;
    }
    
    public void createOrder() {
        // 创建订单代码
        User user = userService.getUser();
        // 订单相关代码
    }
}

public class UserService {
    private OrderService orderService;
    
    public UserService(OrderService orderService) {
        this.orderService = orderService;
    }
    
    public User getUser() {
        // 获取用户代码
        Order order = orderService.getOrder();
        // 用户相关代码
    }
}

在这个改进后的代码中,通过构造函数注入的方式来避免了循环依赖问题,并且更加符合单一设计原则中的“高内聚,低耦合”的原则。

可能有人会说,Spring框架不是提供了三级缓存的方案来解决循环依赖吗?

为了解决循环依赖的问题,Spring确实是使用了三级缓存来管理bean的创建和初始化过程:

具体来说,当Spring创建bean时,会将该bean放入三级缓存中,其中第一级缓存存储已经完成了实例化的bean,第二级缓存存储已经完成了属性注入的bean,第三级缓存存储还未完成属性注入的bean。在注入属性时,Spring会首先从第一级缓存中查找bean,如果找不到则继续到第二级缓存中查找,如果还是找不到则继续到第三级缓存中查找,如果最终还是找不到,则会抛出异常。

既然提供了三级缓存,为什么还要关心注入会不会出现依赖注入的情况呢?

虽然Spring框架提供了三级缓存的方案来解决循环依赖,但是使用@Autowired注解的确可能会引起循环依赖的问题。而且,如果循环依赖链条较长,缓存的效率会受到影响,甚至会导致系统性能下降。

另外,对于大型系统来说,循环依赖是一种设计上的问题,不应该在代码实现时去“绕过”这个问题,而应该通过优化代码结构、拆分模块等方式来解决。

总的来说,虽然Spring框架提供了循环依赖的解决方案,但是在实际使用时,应该避免产生循环依赖的情况,从设计层面上避免这个问题的发生,提高系统的稳定性和可维护性。

注意这里不能使用Setter注入来解决循环依赖问题:

setter注入可以一定程度上避免循环依赖的问题,但并不是完全解决。setter注入是通过在bean初始化完成之后,逐一为属性赋值来完成注入的,因此可以避免在构造函数中进行依赖注入时可能产生的循环依赖问题。

但是如果在setter注入中,存在多个bean相互依赖的情况,仍然有可能产生循环依赖。例如,beanA中有属性a,需要通过setter注入来完成,而属性a又需要引用beanB中的属性b,那么就会产生循环依赖的问题。

为了避免循环依赖的问题,可以通过三种方式解决:

  1. 构造函数注入:在构造函数中注入依赖,从而避免setter注入中可能出现的循环依赖问题。

  2. 使用延迟依赖注入:在注入时不直接注入依赖,而是通过代理对象实现延迟注入,当真正需要使用该依赖时,再进行注入。

  3. 使用工厂模式:通过工厂模式来管理bean的创建和依赖注入,从而解决循环依赖问题。

二、Setter注入

实现代码如下:

@RestController
public class UserController {
    // Setter 注入
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void sayHi() {
        System.out.println("com.java.demo -> do UserController sayHI()");
        userService.sayHi();
    }
}

2.1 优点分析

可以看出,Setter注入其实是比属性注入要麻烦的,但是其也是有优点的,那就是它完全符合单一职责的设计原则,因为每个Setter只针对一个对象。

2.2 缺点分析

2.2.1 不能注入不可变对象

与@Autowired相同,Setter注入是不能注入不可变对象的:

2.2.2 注入对象可被修改

Setter注入提供了setXXX的方法,意味着开发者可以在任意时刻,任何地方,通过调用setXXX方法来改变注入对象。

三、构造方法注入

构造方法注入是Spring官方从4.x之后推荐的注入方式,它的实现代码如下:

@Controller
public class UserController {
    // 构造方法注入
    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
    public void sayHi() {
        System.out.println("com.java.demo -> do UserController sayHI()");
        userService.sayHi();
    }
   
}

注意,如果当前的类中只有一个构造方法,那么@Autowired也可以省略,所以以上代码还可以这一写:

@Controller
public class UserController {
    // 构造方法注入
    private UserService userService;

   
    public UserController(UserService userService) {
        this.userService = userService;
    }
    public void sayHi() {
        System.out.println("com.java.demo -> do UserController sayHI()");
        userService.sayHi();
    }
   
}

3.1 优点分析

3.1.1 可注入不可变对象

使用构造方法是可以注入不可变对象的,以下代码实现:

@Controller
public class UserController {

    private final UserService userService;
    
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }


    public void sayHi() {
        System.out.println("com.java.demo -> do UserController sayHI()");
        userService.sayHi();
    }
}

3.1.2 注入对象不会被修改

由于构造方法只会在对象创建时候执行一次,避免了像Setter注入那样,不存在注入对象被随时(调用)修改的情况。

3.1.3 注入对象会被完全初始化

因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化,这也是构造方法注入的优点之一。

3.1.4 通用性更好

构造方法和属性注入不同,构造方法注入可适用于任何环境,无论是IoC框架还是非IoC框架,构造方法注入的代码都是通用的,所以它的通用性更好。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/449161.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自然语言模型的哲学小谈

近期,以chatGPT为代表的大语言模型表现非常惊艳。“In Context Learning”、“Instruct”1,以及推理能力,很难不让我们期待未来人工智能的发展,同时冷静思考一下为什么自然语言模型能够取得巨大进步。 文章目录 1 放空大脑从0开始…

Ngiinx反向代理和负载均衡

系列文章目录 目录 系列文章目录一、Nginx的反向代理1.代理含义2.反向代理配置模块 二、1.动静分离2.负载均衡实验 总结 一、Nginx的反向代理 1.代理含义 正向代理 正向代理,指的是通过代理服务器 代理浏览器/客户端去重定向请求访问到目标服务器 的一种代理服务…

ARP协议详解

ARP协议详解 文章目录 ARP协议详解ARP协议介绍ARP抓包ARP包解析 ARP协议介绍 ARP(Address Resolution Protocol)是一种用于将网络层地址(如IP地址)转换为数据链路层地址(如MAC地址)的协议,当一…

软件测试概念篇(下)|开发模型与测试模型

作者:爱塔居 专栏:软件测试 作者简介:大三学生,希望同大家一起进步! 文章简介:主要介绍软件生命周期、瀑布模型和螺旋模型两个开发模型,V模型和W模型两个测试模型 文章目录 目录 文章目录 一、软…

【Go】五、网络编程

文章目录 网络编程1、互联网协议介绍2、Socket编程2.1、socket图解2.2、TCP编程2.3、UDP编程2.4、粘包(❌没有详细看代码,但是大概知道怎么实现) 3、Http编程4、WebSocket编程 网络编程 1、互联网协议介绍 2、Socket编程 2.1、socket图解 1…

这家工作室为网飞、亚马逊、迪士尼都制作过特效

过去的十年里,CGEV工作室通过为电影、电视剧以及Netflix、Amazon、Disney等平台制作大量内容,建立了坚不可摧的声誉。我们采访了CGEV团队,了解他们如何在每一个环节都依赖于ftrack Studio。 CGEV全称Compagnie Gnrale des Effets Visuels&…

C++缺省参数

目录 什么是缺省参数缺省参数分类全缺省参数半缺省参数 缺省参数函数的声明缺省参数的使用 什么是缺省参数 缺省参数是声明或定义函数时,为函数的参数指定一个缺省值。 在调用该函数时,如果没有指定实参,那么形参就会采用缺省值,如果指定了实…

【C语言】基础语法5:数组和指针

上一篇:函数和递归 下一篇:字符串和字符处理 ❤️‍🔥前情提要❤️‍🔥   欢迎来到C语言基本语法教程   在本专栏结束后会将所有内容整理成思维导图(结束换链接)并免费提供给大家学习,希望…

C#串口通信从入门到精通(4)——串口调试助手的使用

前言: 关于本文中使用到的串口助手工具,订阅专栏后,加入vip群,即可在群文件免费下载 1、认识串口助手 串口助手主要的功能有以下六个模块,功能介绍分别如下: 1.1 打开、关闭串口、清除接收区数据按钮区 这些按钮用于打开串口、关闭串口、清除接收数据区的数据 1.2 …

IIC协议——同步半双工串行通信方式

文章目录 前言一、简要介绍1、优点2、缺点 二、信号线和连接方式1、信号线2、连接方式2.1 单主设备,单从设备2.2 单主设备,多从设备2.3 多主设备,多从设备 三、数据传输格式1、数据传输过程 二、SPI配置1、传输模式2、地址位宽3、仲裁机制3.1…

【Linux】Linux基本指令(1)

一.前言 从这篇文章开始,博主就开启了Linux学习之路了,本篇文章也是博主的第一篇Linux的文章,今后也会持续不断更新的。 二.理解文件 1.文件 文件文件数据文件属性(所以一个建好的文件就算没有数据,也占用存储空间&am…

[RoarCTF 2019]Easy Calc、攻防世界 ics07、[极客大挑战 2019]EasySQL

[RoarCTF 2019]Easy Calc 进入题目是一个计算器的功能 检查网页源码发现这样一个重要信息 这题有WAF计算功能是通过calc.php这里面的代码执行的&#xff0c;我们去访问一下得到源码 <?php error_reporting(0); if(!isset($_GET[num])){show_source(__FILE__); }else{$str…

传统机器学习(二)逻辑回归算法(二)

传统机器学习(二)逻辑回归算法(二) 之前在传统机器学习(二)逻辑回归算法(一)中介绍了逻辑回归的原理、公式推导、手动python实现及sklearn工具包的使用详解等内容。继续对逻辑回归的使用细节进行介绍。 一、如何得到逻辑回归模型系数 1.1、一个简单的逻辑回归例子 已采集15…

广州蓝景分享—6 个ES13 中非常实用的新 JavaScript 特性

首先作为前端最重要的编程语言JavaScript&#xff0c;每年都在不断发展&#xff0c;让该语言都会通过新功能变得更强大。今天由小蓝跟大家分享6 个ES13 中非常实用的新 JavaScript 特性。 接下来让我们开始&#xff1a; 1.at 当我们想要获取数组的第 N 个元素时&#xff0c;…

Redis底层设计与源码分析---学习笔记

一.Redis为什么快 高速的存储介质 机械硬盘—>固态硬盘—>内存&#xff0c;随机访问的延迟逐渐变小 优良的底层数据结构设计 底层设计用到了hashtable&#xff0c;时间复杂度低 高效的网络IO模型 epoll等&#xff0c;不同平台有不同的实现   高效的线程模型 二.Re…

【论文阅读笔记|ACL2022】Legal Judgment Prediction via Event Extraction with Constraints

论文题目&#xff1a;Legal Judgment Prediction via Event Extraction with Constraints 论文来源&#xff1a;ACL2022 论文链接&#xff1a;https://aclanthology.org/2022.acl-long.48.pdf 代码链接&#xff1a;GitHub - WAPAY/EPM 0 摘要 近年来&#xff0c;虽然法律判…

动态内存管理--->(经典入手版详解)

友友们有时候在开辟内存的时候&#xff0c;所需要的空间大小不太好确定&#xff0c;只有在程序运行的时候才能知道&#xff0c;这时候我们就要尝试一下动态内存开辟空间了&#xff0c;下面来和阿博一起了解并掌握它吧&#x1f917;&#x1f917;&#x1f917; 文章目录 1.为什么…

系统的部署

把报错的三个给删了&#xff0c;系统可以运行了我猜测它对应的是商品分类页面 我觉得它分页器有问题所以研究了下userdto和mtuser的区别 Failed to parse configuration class [com.fuint.fuintApplication]; nested exception is java.lang.IllegalArgumentException: Could…

Mycat数据库中间件 mycat2 路由转发

1.非分片字段查询 Mycat中的路由结果是通过分片字段和分片方法来确定的。例如下图中的一个Mycat分库方案&#xff1a; 根据 tt_waybill 表的 id 字段来进行分片 分片方法为 id 值取 3 的模&#xff0c;根据模值确定在DB1&#xff0c;DB2&#xff0c;DB3中的某个分片 非分片字段…

ESP32设备驱动-LPS25H压阻式压力传感器驱动

LPS25H压阻式压力传感器驱动 文章目录 LPS25H压阻式压力传感器驱动1、LPS25H介绍2、硬件准备3、软件准备4、驱动实现1、LPS25H介绍 LPS25H 是一款超紧凑型绝对压阻式压力传感器。它包括一个单片传感元件和一个能够从传感元件获取信息并向外部世界提供数字信号的 I2C 接口。 传…