Spring 的依赖注入(DI)

news2025/1/9 16:54:31

前言

欢迎来到本篇文章,书接上回,本篇说说 Spring 中的依赖注入,包括注入的方式,写法,该选择哪个注入方式以及可能出现的循环依赖问题等内容。

如果正在阅读的朋友还不清楚什么是「依赖」,建议先看看我第一篇文章,通过 Employee 和 Department 简单说了什么是所谓的依赖。

什么是依赖注入?

依赖注入是一个过程,举个例子:

public class A {
    private B b;
    // 省略 getter 和 setter
    // 省略构造方法
}

现在 A 类 是依赖 B 类的,没有 B,A 什么都不是。Spring IoC 容器创建好 B 的实例对象后并赋值给 A 对象中的 b 属性(成员变量)的过程,就是所谓的「依赖注入」。

当然,这也是所谓的控制反转了,对象 b 的创建不是我们手动 new 创建的,而是 Spring IoC 容器创建的。

使用 DI 原则,可以让代码更加简洁,当对象提供其依赖项时,解耦也更加有效。依赖项 B 注入给 A 的实例对象,A 的实例对象是不会查找它的依赖项 B 的,也不知道依赖项 B 的位置。

依赖注入主要有两种实现方式:基于构造方法的依赖注入基于 Setter 的依赖注入

基于构造方法的依赖注入

基于构造方法的 DI 是通过 Spring IoC 容器调用带有许多参数的构造方法来完成的,每个参数表示一个依赖项。这与上一篇中调用具有特定参数的静态工厂方法来构造 Bean 是几乎等价的。

public class Vehicle {
    
    // Vehicle 依赖于 Producer 对象,或者说 Vehicle 持有一个 Producer 依赖项
    private final Producer producer;
    
    // 构造方法,让 Spring IoC 容器能够注入 Producer 对象给 Vehicle 对象
    public Vehicle(Producer producer) {
        this.producer = producer;
    }
}

注意,这个 Vehicle 类就只是一个普通的 Java Bean,或者说 POJO,没什么特别之处,最普通不过的类了,没有继承或者实现 Spring 指定的任意类。

构造方法中参数的解析

Spring IoC 是通过构造方法参数的解析来匹配需要注入的依赖项。换句话说,解析实际上就是通过匹配参数的类型,来确定注入什么依赖项

如果构造方法的参数中没有存在潜在的歧义,那么在 Bean 被实例化的时候,构造方法中参数的顺序和实例化时进行赋值是一样的。

package cn.god23bin.demo.model;

public class A {
    private B b;
    private C c;
    
    public A(B b, C c) {
        this.b = b;
        this.c = c;
    }
}

单独写这些类,是完成不了依赖注入的,这时候需要配置元数据了,让它与 Java 类相结合,才能发挥 Spring IoC 的作用。

现在假设 B 和 C 是没有任何继承上的关联,也没有任何潜在的歧义,那么我们在配置元数据中的配置是正常的,需要使用到 <constructor-arg/> 标签,该标签只需有一个 ref 属性,即引用(reference),指明 A 的构造方法的参数为 B 和 C,如下:

<beans>
	<bean id="a" class="cn.god23bin.demo.model.A">
		<constructor-arg ref="b"/>
		<constructor-arg ref="c"/>
	</bean>

	<bean id="b" class="cn.god23bin.demo.model.B"/>

	<bean id="c" class="cn.god23bin.demo.model.C"/>
</beans>

我们知道,B 和 C 是属于引用类型,类型是确切的,所以直接使用 ref 属性

如果是基本数据类型,那么赋值时,就可能会有歧义,Spring 不能确定这个值的类型,比如一个基本数据类型的值是 true这时候歧义就出现了,它是布尔类型还是字符串类型呢?这就需要由我们来告诉 Spring 了,就需要使用 typevalue 属性来指定

比如:

package cn.god23bin.demo.model;

public class D {
    private final int money;
    private final String power;
    
    public D(int money, String power) {
        this.money = money;
        this.power = power;
    }
}
<bean id="d" class="cn.god23bin.demo.model.D">
	<constructor-arg type="int" value="25000000"/>
	<constructor-arg type="java.lang.String" value="25"/>
</bean>

当然,除了使用 type 指定基本数据类型,还有两个属性可以解决歧义,分别是 indexname 属性。

index 属性:

使用 index 属性指定构造方法参数的下标,下标是从 0 开始的,来解决参数类型出现歧义的情况。

<bean id="d" class="cn.god23bin.demo.model.D">
	<constructor-arg index="0" value="25000000"/>
	<constructor-arg index="1" value="25"/>
</bean>

name 属性:

使用 name 属性指定构造方法参数的名称来解决歧义。

<bean id="d" class="cn.god23bin.demo.model.D">
	<constructor-arg name="money" value="25000000"/>
	<constructor-arg name="power" value="25"/>
</bean>

基于 Setter 的依赖注入

基于 Setter 的 DI,是在 Spring IoC 容器调用了 Bean 的无参构造方法或者无参的静态工厂方法实例化了 Bean 之后,再来调用 Bean 的 Setter 方法来实现的。

下面这个依旧是普通的 Java Bean,当然你加上相关业务逻辑,就不是纯粹的 Java Bean 了,不过 Spring 依旧能够管理这种对象。

Vehicle:

public class Vehicle {
    
    // Vehicle 持有的 Producer 依赖项
    private Producer pro;
    
    // 同理
    private Author aut;
    
    // 同理
    private int money;
    
    // setter 方法,可以让 Spring IoC 容器调用注入 Producer 对象
    public void setProducer(Producer pro) {
        this.pro = pro;
    }
    
    // 同理
    public void setAut(Author aut) {
        this.aut = aut;
    }
    
    // 同理
    public void setMoney(int money) {
        this.money = money
    }
    
    // 业务逻辑,有关 Producer 的...
}

对应的配置元数据:

<bean id="vehicle" class="cn.god23bin.demo.model.Vehicle">
    <!-- 使用 property 标签的 ref 属性注入引用类型的依赖项 -->
    <property name="pro" ref="producer"/>
    <!-- 使用内嵌 ref 标签 注入 -->
    <property name="aut">
    	<ref bean="author"/>
    </property>
    <property name="money" value="255"/>
</bean>

<bean id="producer" class="cn.god23bin.demo.model.Producer"/>
<bean id="author" class="cn.god23bin.demo.model.Author"/>

Spring IoC 容器(ApplicationContext)支持基于构造方法的 DI 和基于 Setter 的 DI,也支持使用构造方法注入了一部分依赖项后,再使用 Setter 的方式注入其他的依赖项。

我们可以通过配置一个 BeanDefinitionPropertyEditor 来实现这些属性的注入。但是,我们基本不会这样用,而是使用 XML 的 Bean 定义,使用注解的 Bean 定义(比如 @Component@Controller 等),又或者使用 @Bean 这种基于 Java 配置类的方式(@Configuration)来定义。

实际上,这些最终都会转成 BeanDefinition 对象的并被 Spring IoC 使用。

选择哪个 DI 方式?

在代码的编写中,选择使用构造方法注入还是使用 Setter 注入呢?

官网上是这么说的:

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. Note that use of the @Autowired annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.

The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the 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.

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. Management through JMX MBeans is therefore a compelling use case for setter injection.

Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.

简而言之,Spring 团队是推荐用构造方法来完成 DI 的

  • 对于强依赖项,直接用基于构造方法的 DI,这种注入方式能够保证注入的 Bean 对象不可变,并且确保需要的依赖不为空。此外,构造方法注入的依赖总是能够在返回**客户端(调用方)**的时候保证完全初始化的状态。

  • 对于可选的弱依赖项,使用基于 Setter 的 DI。当使用 @Autowired 注解,并将注解用到 Setter 方法上的话,那么这个 Setter 设置的属性就变成强依赖了。

对于构造方法进行 DI ,其中:

  • 依赖不可变:其实说的就是 final 关键字。
  • 依赖不为空:省去了我们对注入的 Bean 的检查,当要实例化 Bean 的时候,由于自己实现了有参数的构造方法,所以不会调用默认构造方法,那么就需要 Spring IoC 容器传入所需要的参数给有参构造方法,所以就两种情况,有该类型的参数传入,那么 OK ;无该类型的参数,那么报错。
  • 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造方法传参之前,要确保注入的内容不为空,那么肯定要调用依赖对象的构造方法完成实例化。而在 Java 类加载实例化的过程中,构造方法是最后一步才执行的,所以返回来的实例化对象都是完全初始化之后的状态。

依赖的处理过程

Spring IoC 容器按如下方式执行 Bean 的依赖处理(Dependency Resolution Process,个人认为把 Resolution 理解成处理,解决等意思比较好)。

  • 根据配置元数据的内容,ApplicationContext 被创建和初始化。这个配置元数据是用来描述所有 Bean 的,它可以是 XML、Java 代码或注解。
  • 对于每个 Bean 来说,它的依赖是以属性、构造方法参数或静态工厂方法的参数(如果你用它代替构造方法)的形式表达的。在 Spring IoC 创建 Bean 的时候,这些依赖会被提供给需要的 Bean。
  • 每个属性或构造方法参数都是要设置的值的实际定义,或对容器中另一个 Bean 的引用。
  • 每个作为值的属性或构造方法参数都会从其指定格式转换为该属性或构造方法参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,如 intlongStringboolean 等等。

当 Spring IoC 容器被创建时,Spring IoC 容器一开始就会校验每个 Bean 的配置。在创建 Bean 之前,Bean 的属性本身不会被设置。

而且当容器被创建时,那些具有单例作用域的 Bean 也默认会被创建。对于其他情况的 Bean,只有在被请求时才会创建。

一个 Bean 的创建是有可能导致一堆 Bean 被创建,这一堆的 Bean 被称为 Bean 图(a graph of beans),因为 Bean 与 Bean 之间可能存在相互依赖,如同社会中的人一样,都有相关的联系,所以形成 Bean 图。

Bean 作用域在进行 Bean 定义的时候可以进行定义,使用 scope 属性即可定义 Bean 的作用域。

循环依赖

当你使用构造函数注入的时候,某种情况下可能产生一个无法解决的循环依赖问题。那什么是循环依赖

举个例子:

public class A {
    private B b;
    
    public A(B b) {
        this.b = b;
    }
}
public class B {
    private A a;
    
    public B(A a){
        this.a = a;
    }
}

如上所示,A 类通过构造方法注入需要 B 类的对象,而 B 类也通过构造方法注入 A 类的一个对象,这种情况就是所谓的循环依赖,A 和 B 之间相互依赖。

A 和 B 之间的循环依赖关系会迫使其中一个 Bean 在未完全初始化之前,被注入到另一个 Bean 中。

这时候,Spring IoC 容器会在运行时检测到这种循环的引用,并抛出一个 BeanCurrentlyInCreationException

有一种解决方案就是:修改 A 类或者 B 类的源代码,使其通过 Setter 方式注入依赖项。

当然,一般情况下,我们可以完全相信 Spring,Spring 会在容器加载时检测配置问题,如检查引用不存在的 Bean 或循环依赖等。它会尽量晚地设置 Bean 的属性以及处理依赖的关系。

假设当你请求一个对象时,如果在创建该对象或其依赖关系时出现问题,已经正确加载的 Spring 容器就会产生异常。所以,这意味着我们需要提前发现这些问题。

默认情况下,ApplicationContext 会预先实例化单例 Bean,所以在创建 Spring IoC 容器时会花费一些时间和内存。但好处是可以在容器创建时发现配置问题,而不是在后续出现。

如果不存在循环依赖关系,一个 Bean 在被注入到另一个 Bean 之前会被完全配置。这意味着当 A 依赖于 B 时,Spring IoC 容器会先完全配置好 B(包括实例化 Bean、设置依赖关系和调用相关的生命周期方法),再调用 A 的 Setter 方法。

依赖注入示例

下面写下示例,快速回顾下上面的两种注入方式,帮助大家更好理解在以 XML 作为配置元数据的情况下,使用基于构造方法的 DI 和基于 Setter 的 DI。

  1. 基于构造方法的 DI:
package cn.god23bin.demo.service;

import cn.god23bin.demo.repository.UserRepository;

public class UserService {
	
    // UserService 的依赖项 UserRepository
	private UserRepository userRepository;

    // 使用构造方法进行 DI,将 UserRepository 注入到 UserService
	public UserService(UserRepository userRepository) {
    	this.userRepository = userRepository;
	}
    
}
<bean id="userService" class="cn.god23bin.demo.service.UserService">
   <constructor-arg ref="userRepository" />
</bean>

<bean id="userRepository" class="cn.god23bin.demo.repository.UserRepository" />
  1. 基于 Setter 的 DI:
package cn.god23bin.demo.service;

import cn.god23bin.demo.repository.UserRepository;

public class UserService {
    
	private UserRepository userRepository;

	public void setUserRepository(UserRepository userRepository) {
		this.userRepository = userRepository;
	}
    
}
<bean id="userService" class="cn.god23bin.demo.service.UserService">
   <property name="userRepository" ref="userRepository" />
</bean>

<bean id="userRepository" class="cn.god23bin.demo.repository.UserRepository" />

总结

本文开头举了个例子讲了什么是依赖注入,实际上就是一个过程,把一个 Bean 注入到另一个 Bean 中的过程。

这个过程,在 Spring 中有两种方式来实现,一种是基于构造方法的 DI,另一种是基于 Setter 的DI。当然,网上讲到的依赖注入,还有好几种方式,目前对于我们来说,知道这两种就 OK。

对于这两种 DI,我们也说了如何选择,具体看官网的说法,推荐的是基于构造方法的 DI,当然,具体问题具体分析,有时候是需要用到基于 Setter 的 DI 的,比如解决循环依赖的时候。

接着也说了依赖的处理过程,简单点就是 Spring 会先根据配置元数据去创建 ApplicationContext 这个作为 Spring IoC 容器,在容器创建时,会验证 Bean 是否正确配置了,默认单例的 Bean 会先被创建等等等的处理。同时可能遇到循环依赖的问题,一种解决方案就是将某一个 Bean 使用 Setter 的方式注入依赖项。

以上,便是本篇文章的内容了。下期咱们讲讲依赖注入的一些细节写法,比如有的依赖项是集合,那么配置元数据中该如何写这个集合,进而实现集合的依赖注入等等。

希望本篇有帮助到大家,有兴趣的话可以关注我,关注这个系列!

最后的最后

希望各位屏幕前的靓仔靓女们给个三连!你轻轻地点了个赞,那将在我的心里世界增添一颗明亮而耀眼的星!

咱们下期再见!

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

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

相关文章

34岁上岸,我终于圆了自己的考研梦

​ 大家好&#xff0c;我是独孤风&#xff0c;一位曾经的港口煤炭工人&#xff0c;目前在某国企任大数据负责人&#xff0c;公众号大数据流动的作者。 ​ 虽然告诉自己要平静&#xff0c;但是当接到EMS录取通知书的那一刻&#xff0c;眼眶还是忍不住有些湿润。今年正好是是东北…

SpringBoot源码分析(1)--@SpringBootApplication注解使用和原理/SpringBoot的自动配置原理详解

文章目录 前言主启动类的配置1、SpringBootApplication注解1.1、SpringBootConfiguration注解验证启动类是否被注入到spring容器中 1.2、ComponentScan 注解ComponentScan 注解解析与路径扫描 1.3、EnableAutoConfiguration注解1.3.1、AutoConfigurationPackage注解1.3.2、Impo…

【MySQL】事务及其隔离性/隔离级别

目录 一、事务的概念 1、事务的四种特性 2、事务的作用 3、存储引擎对事务的支持 4、事务的提交方式 二、事务的启动、回滚与提交 1、准备工作&#xff1a;调整MySQL的默认隔离级别为最低/创建测试表 2、事务的启动、回滚与提交 3、启动事务后未commit&#xff0c;但是…

HTB-Pilgrimage

HTB-Pilgrimage 信息收集80端口立足emily -> root 信息收集 80端口 扫描目录发现存在.git。 通过scrabble获取网站的git文件。 有如下这些文件。 在index.php中使用了magick来处理图像。 正好我们靠git弄了一个&#xff0c;查看一下版本。 这个版本似乎有些不得了的东西…

Quiz 9: Dictionaries | Python for Everybody 配套练习_解题记录

文章目录 课程简介Quiz 9: Dictionaries 单选题&#xff08;1-11&#xff09;编程题Exercise 9.4 课程简介 Python for Everybody 零基础程序设计&#xff08;Python 入门&#xff09; This course aims to teach everyone the basics of programming computers using Python.…

conda的多线程下载工具mamba(解决Anaconda3 solving environment 巨慢的方法)

solving environment为什么会越来越慢&#xff1f; 根据原博的解释以及我查阅的相关资料&#xff0c;这是由于conda在新安装一个包或者更新包时需要搜索当前环境中所有的包的依赖空间&#xff0c;以找到满足所有依赖项的版本&#xff0c;随着用户安装的包越来越多&#xff0c;…

C#核心知识回顾——1.结构体、构造函数、GC、成员属性、索引器

1.结构体&#xff1a; 在 C# 中&#xff0c;结构体是值类型数据结构。它使得一个单一变量可以存储各种数据类型的相关数据。例如我定义了一个结构体&#xff0c;它有两个变量&#xff0c;创建一个这个类型的结构体&#xff0c;通过一个变量名调用多个变量&#xff0c;这些变量可…

Layui时间范围选择器,添加【本周、本月、本季度、本年等常用时间快捷键】

文章目录 1. 界面实现2. JS具体实现2.1 第一种实现2.2 第二种实现 1. 界面实现 <input id"Date_select" type"text" class"form-control" placeholder"请选择时间范围" style"border-radius: 4px;" /><input id&qu…

RuoYi-Vue Swagger 上传文件接口

前言 RuoYi-Vue&#xff1a; v3.8.5swagger 1.6.2 &#xff08;https://github.com/swagger-api/swagger-core, https://gitee.com/mirrors/swagger-core&#xff09; Swagger 上传接口定义 ApiOperation(value "图片上传") PostMapping(value "/upload&qu…

SpringBoo集成MongoDB

一、集成简介 spring-data-mongodb提供了MongoTemplate与MongoRepository两种方式访问mongodb&#xff0c;MongoRepository操作简单&#xff0c;MongoTemplate操作灵活&#xff0c;我们在项目中可以灵活适用这两种方式操作mongodb&#xff0c;MongoRepository的缺点是不够灵活…

OpenMMLab-AI实战营第二期——相关3. RGB语义分割标注图像转为Gray格式的mask

文章目录 1. 转换代码1.1 查看原始图像1.2 转换1.3 cv::IMREAD_GRAYSCALE与CV_BGR2GRAY结果不一致1.3.1 现象描述1.3.2 原因1.3.3 推荐做法 1.4 CV_BGR2GRAY和CV_RGB2GRAY不一致 2. macOS上查看mask&#xff08;使用默认的预览&#xff09; 1. 转换代码 找到了一个语义分割的数…

rc表格卡方检验

一、案例介绍 某医院用三种穴位针刺治疗急性腰扭伤&#xff0c;现在想比较三种穴位针刺效果有无差别&#xff0c;结果汇总如下表&#xff1a; 二、问题分析 本案例想比较三种穴位针刺效果有无差别&#xff0c;可以使用RxC卡方检验进行分析。 通常情况下&#xff0c;共有三种…

uniapp项目 封装一个饼图组件 并且修改显示项的排列方式

需求如下: 真实数据渲染后的完成效果如下: 记录一下代码: <template><view><view style"height: 600rpx;"><l-echart ref"chart" finished"init"></l-echart></view></view> </template><…

【面试】一文知晓---拦截器和过滤器的区别

目录 背景关系图 拦截器和过滤器的区别实操1.过滤器1.1HttpServletRequestWrapper1.2 OncePerRequestFilter1.3 配置 2.拦截器2.1登录拦截2.2配置 3.监听器 三、注意1.静态资源问题2.登录拦截ajax重定向 总结 背景 关系图 然后具体执行流程如下&#xff1a; 拦截器和过滤器的区…

IDEA创建一个Servlet项目(tomcat10)

一、创建maven项目 org.apache.maven.archetypes:maven-archetype-webapp 二、增加Servlet依赖 tomcat9及以前依赖 <!--加入servlet依赖&#xff08;servlet的jar&#xff09;--><dependency><groupId>javax.servlet</groupId><artifactId>ja…

MoblieNet

论文信息 论文名称&#xff1a;MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications 论文地址&#xff1a;https://arxiv.org/abs/1704.04861 研究背景和研究意义 之前的网络都倾向于将网络做得又大又深&#xff0c;并且不考虑网络的速度&…

测试CefSharp.WinForms的基本用法

微信公众号“dotNET全栈开发”的文章《C#使用CefSharp内嵌网页-并给出C#与JS的交互示例》介绍了CefSharp的基本用法。CefSharp支持在.net程序中内置Chromium&#xff0c;它是Chromium Embedded Framework (CEF) 的轻量化封装。   CefSharp面向Winform、wpf等提供对应的NuGet包…

SpringBoot初始化接口CommandLineRunner

CommandLineRunner的使用 接口定义使用执行顺序使用 Order 注解实现Orderd接口排序总结 接口定义 Spring官方给出的接口定义 package org.springframework.boot;FunctionalInterface public interface CommandLineRunner {void run(String... args) throws Exception; }在 Sp…

卡方检验之多重比较

一、案例介绍 某医师研究物理疗法、药物治疗和外用膏药3种疗法治疗周围性面神经麻痹的疗效&#xff0c;通过整体卡方检验已经得知3种疗法有效率的差异有统计学意义&#xff08;χ221.0377&#xff0c;p0.000&#xff09;的结论。现在想进一步知道&#xff0c;具体是哪两种疗法…

Android后台应用开启前台服务---android8到android12梳理

1、Android 8.0 异常报错 在Android 8.0 系统中&#xff0c;处于后台的应用想要开启前台服务&#xff0c;必须满足两点&#xff1a; 在Activity中调用startForegroundService()方法所调起的Service必须执行startForeground(int id, Notification notification)方法&#xff0…