《Spring Framework实战》9:4.1.4.依赖注入

欢迎观看《Spring Framework实战》视频教程

典型的企业应用程序不是由单个对象(或Spring术语中的bean)组成。即使是最简单的应用程序也有几个对象协同工作,以呈现最终用户所认为的连贯应用程序。下一节将解释如何从定义多个独立的bean定义到一个完全实现的应用程序,在这个应用程序中,对象协作以实现目标。

本部分摘要

  1. 依赖注入
  2. 依赖项和配置详细信息
  3. 使用 depends-on
  4. 延迟初始化的 Bean
  5. 自动装配协作者
  6. 方法注入
        1. 依赖注入

依赖注入(Dependency injection)(DI)是一种过程,对象仅通过构造函数参数、工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖关系(即它们所使用的其他对象)。然后,容器在创建bean时注入这些依赖项。这个过程从根本上说是bean本身的逆过程(因此称为控制反转),bean通过使用类的直接构造或服务定位器模式来控制其依赖关系的实例化或位置。

DI原则使代码更清晰,当对象提供了它们的依赖关系时,解耦更有效。对象不查找其依赖关系,也不知道依赖关系的位置或类。因此,你的类变得更容易测试,特别是当依赖关系是在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。

DI存在两种主要变体:基于构造函数的依赖注入基于设置器的依赖注入

          1. 基于构造函数的依赖注入

基于构造的依赖注入是由调用具有大量参数的构造函数的容器完成的,每个参数代表一个从属关系。要求static 工厂方法与特定的参数构造的Bean几乎是等价的,这个讨论将参数处理为构造函数和static 类似的工厂方法。下面的例子显示了一类只能依赖于结构注入的类:

Java

public class SimpleMovieLister {

// the SimpleMovieLister has a dependency on a MovieFinder

private final MovieFinder movieFinder;

// a constructor so that the Spring container can inject a MovieFinder

public SimpleMovieLister(MovieFinder movieFinder) {

this.movieFinder = movieFinder;

}

// business logic that actually uses the injected MovieFinder is omitted...

}

注意,这个类没有什么特别的。它是一个POJO,它不依赖于容器特定的接口、基类或注释。

            1. 构造函数参数解析

使用参数的类型进行构造参数解析匹配。如果在一个Bean定义的构造函数参数中存在正态歧义,那么在一个Bean定义中定义构造函数参数的顺序是,当这些参数被实例化时,这些参数将被提供给适当的构造函数。考虑下列类别:

Java

package x.y;

public class ThingOne {

public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {

// ...

}

}

假设ThingTwo和ThingThree类没有继承关系,则不存在潜在的歧义。因此,以下配置工作正常,您不需要在<constructor arg/>元素中显式指定构造函数参数索引或类型。

<beans>

<bean id="beanOne" class="x.y.ThingOne">

<constructor-arg ref="beanTwo"/>

<constructor-arg ref="beanThree"/>

</bean>

<bean id="beanTwo" class="x.y.ThingTwo"/>

<bean id="beanThree" class="x.y.ThingThree"/>

</beans>

当引用另一个bean时,类型是已知的,可以进行匹配(就像前面的例子一样)。当使用简单类型时,例如<value>true</value>,Spring无法确定值的类型,因此在没有帮助的情况下无法按类型匹配。考虑以下类:

Java

package examples;

public class ExampleBean {

// Number of years to calculate the Ultimate Answer

private final int years;

// The Answer to Life, the Universe, and Everything

private final String ultimateAnswer;

public ExampleBean(int years, String ultimateAnswer) {

this.years = years;

this.ultimateAnswer = ultimateAnswer;

}

}

            1. 构造函数参数类型匹配

在前面的场景中,如果您通过type属性显式指定构造函数参数的类型,则容器可以使用简单类型的类型匹配,如下例所示:

<bean id="exampleBean" class="examples.ExampleBean">

<constructor-arg type="int" value="7500000"/>

<constructor-arg type="java.lang.String" value="42"/>

</bean>

            1. 构造者参数索引

您可以使用index属性显式指定构造函数参数的索引,如下例所示:

<bean id="exampleBean" class="examples.ExampleBean">

<constructor-arg index="0" value="7500000"/>

<constructor-arg index="1" value="42"/>

</bean>

除了解决多个简单值的歧义外,指定索引还可以解决构造函数有两个相同类型参数的歧义。

该指数以0为基础。

            1. 构造者参数名称

您还可以使用构造函数参数名称进行值消歧,如下例所示:

<bean id="exampleBean" class="examples.ExampleBean">

<constructor-arg name="years" value="7500000"/>

<constructor-arg name="ultimateAnswer" value="42"/>

</bean>

请记住,为了开箱即用,您的代码必须在启用了-parameters标志的情况下编译,以便Spring可以从构造函数中查找参数名称。如果您不能或不想使用-parameters标志编译代码,可以使用@ConstructorProperties JDK注释显式命名构造函数参数。然后,示例类必须如下所示:

Java

package examples;

public class ExampleBean {

// Fields omitted

@ConstructorProperties({"years", "ultimateAnswer"})

public ExampleBean(int years, String ultimateAnswer) {

this.years = years;

this.ultimateAnswer = ultimateAnswer;

}

}

          1. 基于设置的依赖注入

基于Setter的DI是通过容器在调用无参数构造函数或无参数静态工厂方法实例化bean后调用bean上的Setter方法来实现的。

以下示例显示了一个只能通过使用纯setter注入进行依赖注入的类。这个类是传统的Java。它是一个POJO,不依赖于容器特定的接口、基类或注释。

Java

public class SimpleMovieLister {

// the SimpleMovieLister has a dependency on the MovieFinder

private MovieFinder movieFinder;

// a setter method so that the Spring container can inject a MovieFinder

public void setMovieFinder(MovieFinder movieFinder) {

this.movieFinder = movieFinder;

}

// business logic that actually uses the injected MovieFinder is omitted...

}

ApplicationContext为其管理的bean支持基于构造函数基于设置器的DI。在通过构造函数方法注入了一些依赖关系后,它还支持基于setter的DI。您可以以BeanDefinition的形式配置依赖关系,并将其与PropertyEditor实例结合使用,以将属性从一种格式转换为另一种格式。然而,大多数Spring用户并不直接使用这些类(即以编程方式),而是使用XML bean定义、带注释的组件(即用@Component、@Controller等注释的类)或基于Java的@Configuration类中的@bean方法。然后,这些源在内部转换为BeanDefinition的实例,并用于加载整个Spring IoC容器实例。

基于构造函数还是基于设置器的DI?

由于可以混合使用基于构造函数和基于设置器的DI,因此将构造函数用于强制依赖关系,将设置器方法或配置方法用于可选依赖关系是一个很好的经验法则。请注意,在setter方法上使用@Autowired注释可以使属性成为必需的依赖项;然而,构造函数注入和参数的编程验证是可取的。

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

Setter注入应该主要用于可选依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖关系的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象易于重新配置或稍后重新注入。因此,通过JMX MBean进行管理是setter注入的一个引人注目的用例。

使用对特定类最有意义的DI样式。有时,在处理您没有源代码的第三方类时,会为您做出选择。例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。

          1. 依赖解过程

容器执行bean依赖解析,如下所示:

  1. ApplicationContext是使用描述所有bean的配置元数据创建和初始化的。配置元数据可以通过XML、Java代码或注释指定。
  2. 对于每个bean,它的依赖关系都以属性、构造函数参数或静态工厂方法的参数的形式表示(如果你使用它而不是普通的构造函数)。这些依赖关系在bean实际创建时提供给bean。
  3. 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
  4. 作为值的每个属性或构造函数参数都会从其指定格式转换为该属性或构造函数的实际类型。默认情况下,Spring可以将以字符串格式提供的值转换为所有内置类型,如int、long、string、boolean等。

Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,bean属性本身不会被设置。创建容器时,会创建单例作用域并设置为预实例化(默认)的Bean。作用域在Bean作用域中定义。否则,bean仅在被请求时创建。创建bean可能会导致创建bean图,因为bean的依赖关系及其依赖关系(等等)被创建和分配。请注意,这些依赖关系之间的解析不匹配可能会在稍后出现,即在首次创建受影响的bean时出现。

循环依赖关系

如果您主要使用构造函数注入,则可能会创建无法解决的循环依赖场景。

例如:类A通过构造函数注入需要类B的实例,类B通过构造函数注入要求类A的实例。如果将bean配置为类A和B相互注入,Spring IoC容器将在运行时检测到此循环引用,并抛出BeanCurrentlyCreationException。

一种可能的解决方案是编辑一些类的源代码,由setter而不是构造函数配置。或者,避免构造函数注入,只使用setter注入。换句话说,虽然不建议这样做,但您可以使用setter注入配置循环依赖关系。

与典型的情况(没有循环依赖)不同,bean a和bean B之间的循环依赖迫使其中一个bean在完全初始化之前注入另一个bean(经典的鸡和蛋场景)。

一般来说,你可以相信Spring会做正确的事情。它在容器加载时检测配置问题,例如对不存在的bean的引用和循环依赖关系。Spring在bean实际创建时尽可能晚地设置属性并解析依赖关系。这意味着,如果创建对象或其依赖项时出现问题,正确加载的Spring容器可以在以后请求对象时生成异常——例如,bean因缺少或无效属性而抛出异常。一些配置问题的可见性可能会延迟,这就是ApplicationContext实现默认预实例化单例bean的原因。在实际需要之前创建这些bean需要一些前期时间和内存,但在创建ApplicationContext时,而不是以后,您会发现配置问题。您仍然可以覆盖此默认行为,以便单例bean延迟初始化,而不是急切地预实例化。

如果不存在循环依赖关系,当一个或多个协作bean被注入依赖bean时,每个协作bean在注入依赖bean之前都会被完全配置。这意味着,如果bean A依赖于bean B,Spring IoC容器会在调用bean A的setter方法之前完全配置bean B。换句话说,bean被实例化(如果它不是预实例化的单例),其依赖关系被设置,相关的生命周期方法(如配置的init方法或InitializingBean回调方法)被调用。

          1. 依赖注入的例子

下面的示例使用基于xml的配置元数据为基于环境的di。一个SpringXML配置文件的一小部分指定了如下的一些Bean定义:

<bean id="exampleBean" class="examples.ExampleBean">

<!-- setter injection using the nested ref element -->

<property name="beanOne">

<ref bean="anotherExampleBean"/>

</property>

<!-- setter injection using the neater ref attribute -->

<property name="beanTwo" ref="yetAnotherBean"/>

<property name="integerProperty" value="1"/>

</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>

<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

下面的示例显示相应的ExampleBean 班级:

Java

public class ExampleBean {

private AnotherBean beanOne;

private YetAnotherBean beanTwo;

private int i;

public void setBeanOne(AnotherBean beanOne) {

this.beanOne = beanOne;

}

public void setBeanTwo(YetAnotherBean beanTwo) {

this.beanTwo = beanTwo;

}

public void setIntegerProperty(int i) {

this.i = i;

}

}

在前面的示例中,设置者声明与XML文件中指定的属性匹配。下面的例子是使用基于建筑的数据交换:

<bean id="exampleBean" class="examples.ExampleBean">

<!-- constructor injection using the nested ref element -->

<constructor-arg>

<ref bean="anotherExampleBean"/>

</constructor-arg>

<!-- constructor injection using the neater ref attribute -->

<constructor-arg ref="yetAnotherBean"/>

<constructor-arg type="int" value="1"/>

</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>

<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

下面的示例显示相应的ExampleBean 班级:

Java

public class ExampleBean {

private AnotherBean beanOne;

private YetAnotherBean beanTwo;

private int i;

public ExampleBean(

AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

this.beanOne = anotherBean;

this.beanTwo = yetAnotherBean;

this.i = i;

}

}

在Bean定义中指定的构造函数参数被用作构造函数的参数。ExampleBean .

现在考虑这个例子的一个变体,在这里,使用的不是构造函数,而是Spring调用一个static 返回对象实例的工厂方法:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">

<constructor-arg ref="anotherExampleBean"/>

<constructor-arg ref="yetAnotherBean"/>

<constructor-arg value="1"/>

</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>

<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

下面的示例显示相应的ExampleBean 班级:

Java

public class ExampleBean {

// a private constructor

private ExampleBean(...) {

...

}

// a static factory method; the arguments to this method can be

// considered the dependencies of the bean that is returned,

// regardless of how those arguments are actually used.

public static ExampleBean createInstance (

AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

ExampleBean eb = new ExampleBean (...);

// some other operations...

return eb;

}

}

辩论static 工厂方法由<constructor-arg/> 元素,和构造函数实际上被使用的完全一样。由工厂方法返回的类的类型不必与包含static 工厂方法(尽管在本例中是)。实例(非静态)工厂法可以使用基本相同的方式使用。factory-bean 代替了class 所以我们在这里不讨论这些细节。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32-笔记37-吸烟室管控系统项目

一、项目需求 1. 使用 mq-2 获取环境烟雾值&#xff0c;并显示在 LCD1602 上&#xff1b; 2. 按键修改阈值&#xff0c;并显示在 LCD1602 上&#xff1b; 3. 烟雾值超过阈值时&#xff0c;蜂鸣器长响&#xff0c;风扇打开&#xff1b;烟雾值小于阈值时&#xff0c;蜂鸣器不响…

云安全博客阅读(三)

WAF强固之盾&#xff1a;机器学习赋能下的语义分析 WAF 中&#xff0c;传统的基于正则的检测方法依赖正则的运营更新&#xff0c;以不断防护新的攻击方法&#xff1b; 主要流程为&#xff1a;HTTP包 -> payload解码 -> 正则匹配 但是&#xff0c;攻击者可以通过修改攻…

个人博客搭建(二)—Typora+PicGo+OSS

个人博客站—运维鹿: http://www.kervin24.top CSDN博客—做个超努力的小奚&#xff1a; 做个超努力的小奚-CSDN博客 一、前言 博客搭建完一直没有更新&#xff0c;因为WordPress自带的文档编辑器不方便&#xff0c;以前用CSDN写作的时候&#xff0c;习惯了Typora。最近对比了…

spring boot 集成 knife4j

1、knife4j介绍以及环境介绍 knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名knife4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!其底层是对Springfox的封装&#xff0c;使用方式也和Springfox一致&#xff0c;只是对接口…

案例解读 | 香港某多元化综合金融企业基础监控+网管平台建设实践

PART01 项目背景 01客户简介案例客户是一家创立20多年的香港某多元化综合金融企业&#xff0c;其业务范围涵盖证券、期货、资产管理、财富管理等&#xff0c;凭借广泛的业务网络和多元化的金融服务产品&#xff0c;在市场中拥有显著的影响力。02痛点分析随着业务版图的持续拓展…

KCP解读:C#库类图

本文是系列文章中的一篇&#xff0c;内容由浅到深进行剖析&#xff0c;为了方便理解建议按顺序一一阅读。 KCP技术原理 KCP解读&#xff1a;基础消息收发 KCP解读&#xff1a;重传机制 KCP解读&#xff1a;滑动窗口 KCP解读&#xff1a;拥塞控制 本系列的源码基于https://gith…

Nginx:Stream模块

什么是 Stream 模块? Stream 模块 是 Nginx 的一个核心模块,专为处理非 HTTP 协议的流量(TCP 和 UDP 流量)而设计。它可以用来负载均衡和代理 TCP 或 UDP 连接,适用于多种应用场景,如: 数据库集群(MySQL、PostgreSQL 等)邮件服务器(SMTP、IMAP、POP3)游戏服务器VoI…

Profinet转EtherNet/IP网关连接AB PLC的应用案例

某大型制造企业的生产车间同时采用了西门子 S7 - 1500 PLC 作为核心控制系统的一部分&#xff0c;负责主要生产流程的控制与数据处理&#xff1b;而在特定生产环节&#xff0c;由于历史设备遗留或工艺配套需求&#xff0c;存在使用 AB PLC 的情况。这就导致了在整个自动化生产系…

arcgisPro加载CGCS2000天地图后,如何转成米单位

1、导入加载的天地图影像服务&#xff0c;一开始是经纬度显示的。 2、右键地图&#xff0c;选择需要调整的投影坐标&#xff0c;这里选择坐标如下&#xff1a; 3、点击确定后&#xff0c;就可以调整成米单位的了。 4、切换后结果如下&#xff1a; 如有需要&#xff0c;可调整成…

2025新春烟花代码(二)HTML5实现孔明灯和烟花效果

效果展示 源代码 <!DOCTYPE html> <html lang"en"> <script>var _hmt _hmt || [];(function () {var hm document.createElement("script");hm.src "https://hm.baidu.com/hm.js?45f95f1bfde85c7777c3d1157e8c2d34";var …

机器人技术:ModbusTCP转CCLINKIE网关应用

在当今自动化生产与智能制造领域&#xff0c;ModbusTCP转CC-LinkIE网关KJ-MTCPZ-CCIES的应用正日益成为提升生产效率、实现设备间高效通信的重要技术手段。这一转换技术不仅打破了不同通信协议间的壁垒&#xff0c;还为机器人产品的应用提供了更为广阔的舞台。ModbusTCP作为一种…

Openwrt @ rk3568平台 固件编译实践(二)- ledeWRT版本

目录 ledeWRT介绍固件编译下载代码修改feed源更新并安装编译第三方软件包制作用于eMMC烧写的rootfs基于lede发行版验证烧写rk3568.img, LEDE wrt启动成功refhttps://blog.csdn.net/zc21463071/article/details/106751361介绍rk3568平台下, lede 大神版 openwrt固件的下载、编译…

【linux系统之redis6】redisTemplate的使用方法

新版本的application.yml配置文件 spring:data:redis:host: 192.168.1.102port: 6379lettuce:pool:max-active: 8min-idle: 1max-idle: 0max-wait: 100redisTemplate使用方法 <dependencies><dependency><groupId>org.springframework.boot</groupId>…

【算法】字符串算法技巧系列

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 引入&#xff1a;字符串相关算法技巧 1&#xff1a;字符串转数组 2&#xff1a;子字符串 3&#xff…

掌握正则表达式:从入门到精通的实战指南

文章目录 &#x1f30d;一.正则表达式❄️1.为什么学习正则表达式❄️ 2.基本介绍❄️3.分析底层实现 &#x1f30d;二.正则表达式的语法❄️1.字符匹配❄️2.量词❄️3.定位符4.分组和引用❄️6.非贪婪匹配❄️7.分支结构❄️实际应用 &#x1f30d; 三.正则标表达式的三个常用…

leetcode 5. 最长回文子串

题目如下 本题可以这么来想设有一个回文串s"112211"当我们去掉左右两边的"1"时s任然是回文串。 反过来说现有字符串 "x1221y"(x,y都是未知字符)当且仅当x y时这个字符串是回文串。 故我们可以令i j为某一个字符串的左右两端然后有如下情况: i…

BoltzGnu Boltztrap数据绘图脚本

BoltzGnu包含四个Gnuplot脚本&#xff0c;允许绘制BoltzTraP输出数据。 下载网址&#xff1a; https://github.com/K4ys4r/BoltzGnu 1. pTRACE_E.gp -> To plot Trasport proporties as a function of energy at define Temperature2. pTRACE_E_multT.gp -> To pl…

AIA - APLIC之三(附APLIC处理流程图)

本文属于《 RISC-V指令集基础系列教程》之一,欢迎查看其它文章。 1 APLIC复位 APLIC复位后,其所有状态都变得有效且一致,但以下情况除外: 每个中断域的domaincfg寄存器(spec第 4.5.1 节);可能是machine-level interrupt domain的MSI地址配置寄存器(spec第4.5.3 和4.5…

【MySQL】深度学习数据库开发技术:使用CC++语言访问数据库

**前言&#xff1a;**本节内容介绍使用C/C访问数据库&#xff0c; 包括对数据库的增删查改操作。 主要是学习一些接口的调用&#xff0c; 废话不多说&#xff0c; 开始我们的学习吧&#xff01; ps:本节内容比较容易&#xff0c; 友友们放心观看哦&#xff01; 目录 准备mysql…

微信小程序map组件所有markers展示在视野范围内

注意&#xff1a;使用include-points属性不生效&#xff0c;要通过createMapContext实现 <template><view class"map-box"><map id"map" class"map" :markers"markers" :enable-traffic"true" :enable-poi&…