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

目录

一、属性注入(@Autowired)

1.1 优点分析

1.2 缺点分析

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

1.2.2 兼容性不好

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

1.2.4 代码举例:

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

1.2.6 @Resource与@Autowired的区别

二、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的创建和依赖注入,从而解决循环依赖问题。

1.2.6 @Resource与@Autowired的区别

在进行类注入的时候,除了可以使用@Autowired之外,还可以使用@Resource进行注入,如下代码所示:

@Autowired和@Resource的区别

  • 出身不同:@Autowired来自于Spring,而Resource来自于JDK的注解。
  • 使用时设置的参数不同:相比于@Autowired来说,@Resource支持更多的参数设置,例如name设置——可以根据名称获取Bean。
  • @Autowired可用于Setter注入,构造方法注入和熟悉注入,而@Resource只能用于Setter注入和属性注入,不能用于构造方法注入。
  • 查找顺序不同:@Autowired是先根据类型查找,再根据名称查找,而@Resource是根据名称查找,再根据类型查找。

同⼀类型多个 @Bean 报错

代码报错:

这是因为我们在Spring中并没有存储user1的Bean对象:

@Resource可配合参数name在Spring中查找类:

而@Autowired可以配合@Qualifier使用:

​​​​​​​

二、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框架,构造方法注入的代码都是通用的,所以它的通用性更好。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

初识MySQL数据库——“MySQL数据库”

各位CSDN的uu们你们好呀,小雅兰好久没有更文啦,确实是心有余而力不足,最近学习的内容太难了,这篇博客又是小雅兰的新专栏啦,主要介绍的是一些MySQL数据库的知识点,下面,让我们进入初识MySQL数据…

【Java入门合集】第二章Java语言基础(二)

【Java入门合集】第二章Java语言基础(二) 博主:命运之光 专栏JAVA入门 学习目标 掌握变量、常量、表达式的概念,数据类型及变量的定义方法; 掌握常用运算符的使用; 掌握程序的顺序结构、选择结构和循环结构…

计算机是如何工作的

目录 一、冯诺依曼体系: 二、操作系统 三、进程 3.1 PCB(进程控制块)— 描述进程属性的结构体 3.2 CPU分配 — 进程调度 3.3 内存分配 — 虚拟地址 3.4 进程间通信 一、冯诺依曼体系: CPU中央处理器(运算器控制…

算法记录 | Day45 动态规划

70.爬楼梯 (进阶) 改为:一步一个台阶,两个台阶,三个台阶,…,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢? 1阶,2阶,… m阶就是物品,楼顶…

什么是VLAN?为什么要划分VLAN?

VLAN(Virtual Local Area Network)即虚拟局域网,是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。每个VLAN是一个广播域,VLAN内的主机间可以直接通信,而VLAN间则不能直接互通。这样,广播报文就被限制在一个VLAN内。 一、为…

搭建electron-vue上

electron-vue 准备工作修改package.jsonappveyor.yml.travis.yml.gitignore.eslintrc.js.eslintignore.babelrcsrc/renderer/main.jssrc/renderer/App.vuesrc/renderer/store/index.jssrc/renderer/store/modules/Counter.jssrc/renderer/store/modules/Counter.jssrc/renderer…

定时任务方案实现与对比

定时任务分类 定时任务分为分布式定时任务和单机定时任务两个大的方向,他们的适用场景不同。 单机定时任务在单台计算机上运行,其执行结果和单台机器上的数据有关,如对本地机器的缓存做核对、清理日志等。它的 优点 是简单易用,无…

【STM32】基础知识 第十课 CubeMx

【STM32】基础知识 第十课 CubeMx STM32 CubeMX 简介安装 JAVACubeMX 安装新建 STM32 CubeMX 工程步骤新建工程时钟模块配置GPIO 配置生成源码 main.c STM32 CubeMX 简介 CubeMX (全称 STM32CubeMX) 是 ST 公司推出的一款用于 STM32 微控制器配置的图形化工具. 它能帮助开发者…

云原生Istio基本介绍

目录 1 什么是Istio2 Istio特征2.1 连接2.2 安全2.3 策略2.4 观察 3 Istio与服务治理3.1服务治理的三种形态 4 Istio与Kubernetes4.1 Kubernetes介绍4.2 Istio是Kubernetes的好帮手4.3 Kubernetes是Istio的好基座 5 Istio与服务网格5.1 时代选择服务网格5.2 服务网格选择Istio …

JAVA医院管理云HIS统计报表子系统、系统管理字系统功能实现

一、统计报表子系统 统计报表子系统功能模块:包括门诊收入汇总、住院收入汇总、收费统计报表、收费明细报表、 缴款日报、门诊收费汇总、住院科室日志、住院结算汇总、医疗项目统计、检查项目统计、 检验项目统计、月末收支汇总、药品进销存统计。 (1…

Matlab实现多个窗口间的数据传递(不用GUIDE)

在用多个matlab的figure进行数据交互时,数据传入是较为简单的,可以直接用function的形参实现,但如何把数据传回,是个比较麻烦的问题。 在GUIDE下,系统自动生成了output_fcn函数,可以用它来实现从子窗口到主…

Javaweb | 状态管理:Session、Cookie

💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! 状态管理 问题引入 HTTP协议是无转态的,不能保存提交的信息如果用户发来一个新的请求,服务器无法知道它是否与上次的请求联系对于那些需要多次…

springmvc

title: 3 springmvc date: ‘2023-3-29’ Author:glls Version:9.0.2 文章目录 一、SpringMVC1.1 引言1.2 MVC架构1.2.1 概念1.2.2 好处 二、开发流程2.1 导入依赖2.2 配置核心(前端)控制器2.3 后端控制器2.4 配置文件2.5 访问 三、接收请求参数3.1 基本…

时序预测 | Matlab实现SSA-GRU、GRU麻雀算法优化门控循环单元时间序列预测(含优化前后对比)

时序预测 | Matlab实现SSA-GRU、GRU麻雀算法优化门控循环单元时间序列预测(含优化前后对比) 目录 时序预测 | Matlab实现SSA-GRU、GRU麻雀算法优化门控循环单元时间序列预测(含优化前后对比)预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现SSA-GRU、GRU麻雀算法…

Oracle删除列操作:逻辑删除和物理删除

概念 逻辑删除:逻辑删除并不是真正的删除,而是将表中列所对应的状态字段(status)做修改操作,实际上并未删除目标列数据或恢复这些列占用的磁盘空间。比如0是未删除,1是删除。在逻辑上数据是被删除了&#…

数据结构与算法(小议递归)

文章目录 前言一、递归是什么?二、在什么时候适用递归1.测试一下 总结 前言 递归是一种常用的算法设计,递归就是一种循环推理。简单来说就是调用原算法本身的算法。 这里主要探讨递归的使用, 一、递归是什么? 用一个简单的例子来…

js逆向之rpc远程调用(你强任你强,我无视一切)

一、找到加密函数位置 二、在其下面注入ws服务 (1)注入准备 资源>>替换>>随便选一个空文件夹 (2)进行注入 进行(1)操作后可直接编辑js代码了,做以下修改 (function() {var ws new WebSocket(…

【Java笔试强训 20】

🎉🎉🎉点进来你就是我的人了博主主页:🙈🙈🙈戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔🤺🤺🤺 目录 一、选择题 二、编程题 🔥字符串反…

让GPT成为护理专家 - 护士的工作如此简单

引子    书接上文《GPT接入企微应用 - 让工作快乐起来》,我把GPT接入了企微应用,不少同事都开始尝试起来了。有的浅尝辄止,有的刨根问底,五花八门,无所不有。这里摘抄几份: “帮我写一份表白信&#xff…