Spring系列4:依赖注入的2种方式
本文内容
- 基于构造器的依赖注入
- 基于setter的依赖注入
基于构造器的依赖注入
案例
定义2个简单的bean类,BeanOne 和 BeanTwo,前者依赖后者。
package com.crab.spring.ioc.demo02;
public class BeanTwo {
}
package com.crab.spring.ioc.demo02;
/**
* @author zfd
* @version v1.0
* @date 2022/1/12 16:59
*/
public class BeanOne {
private int age;
private String name;
private BeanTwo beanTwo;
/**
* 构造函数,用于依赖注入,定义3个依赖
* @param age
* @param name
* @param beanTwo
*/
public BeanOne(int age, String name, BeanTwo beanTwo) {
this.age = age;
this.name = name;
this.beanTwo = beanTwo;
}
@Override
public String toString() {
return "BeanOne{" +
"age=" + age +
", name='" + name + '\'' +
", beanTwo=" + beanTwo +
'}';
}
}
通过xml配置文件实现bean定义和依赖注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bean2" class="com.crab.spring.ioc.demo02.BeanTwo"/>
<bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne">
<constructor-arg name="age" index="0" type="int" value="20"/>
<constructor-arg name="name" index="1" type="java.lang.String" value="xxx"/>
<constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
</bean>
</beans>
来个测试类验证下注入
package com.crab.spring.ioc.demo02;
/**
* @author zfd
* @version v1.0
* @date 2022/1/12 17:09
*/
public class demo02Test {
@Test
public void test_construct() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml");
BeanOne bean1 = context.getBean("bean1", BeanOne.class);
System.out.println(bean1);
context.close();
}
输出如下
BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@5204062d}
对照配置文件BeanOne的3个依赖都通过构造器的方式进行注入了,符合预期,很简单。
constructor-arg详解
标签constructor-arg支持的元素列表如下。
| 元素名 | 作用 |
|---|---|
| name | 参数名 |
| index | 参数索引,0开始 |
| type | 参数类型 |
| value | 值 |
| ref | bean引用 |
例如案例中的配置
<bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne">
<constructor-arg name="age" index="0" type="int" value="20"/>
<constructor-arg name="name" index="1" type="java.lang.String" value="xxx"/>
<constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
</bean>
注意: 在没有引起歧义的情况下,上面的部分元素并不是都必须配置的。如指定了index时可以定位参数位置,那么name是可以不配置的,又如通过ref引用依赖bean,type可以省略
<bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne">
<constructor-arg index="0" type="int" value="20"/>
<constructor-arg index="1" type="java.lang.String" value="xxx"/>
<constructor-arg index="2" ref="bean2"/>
</bean>
注意事项
当在<constructor />使用 name元素指定构造函数中的参数名时,尤其要方法参数名编译后是否保留的情况。举个例子
// 编译前的方法参数
public BeanOne(int age, String name, BeanTwo beanTwo)
// 编译后的方法参数
public BeanOne(int var1, String var2, BeanTwo var3)
编译后方法参数被编译成var1这种形式会导致仅指定name的构造注入失效。
如何解决?提供2种方法
Maven编译插件添加编译参数,保留参数名,pom文件下增加如下插件配置<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<encoding>UTF8</encoding>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin> </plugins>
</build>
使用
@ConstructorPropertiesJDK 注释显式命名构造函数参数/**
* 构造函数,用于依赖注入,定义3个依赖
* @param age
* @param name
* @param beanTwo
*/
@ConstructorProperties({"age", "name", "beanTwo"}) // 显式声明构造参数名称
public BeanOne(int age, String name, BeanTwo beanTwo) {
this.age = age;
this.name = name;
this.beanTwo = beanTwo;
}
基于setter的依赖注入
基于 Setter 的 DI 是通过容器在调用无参数构造函数或无参数静态工厂方法来实例化 bean 后调用 bean 上的 setter 方法来完成的。
来一个简单类BeanThree依赖BeanOne和BeanTwo并提供了Setter方法来设置。
package com.crab.spring.ioc.demo02;
/**
* @author zfd
* @version v1.0
* @date 2022/1/13 8:18
*/
public class BeanThree {
private BeanTwo beanTwo;
private BeanOne beanOne;
public void setBeanTwo(BeanTwo beanTwo) {
this.beanTwo = beanTwo;
}
public void setBeanOne(BeanOne beanOne) {
this.beanOne = beanOne;
}
}
对应的配置文件可以通过标签property中的ref或name来设置属性引用或是属性值
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bean2" class="com.crab.spring.ioc.demo02.BeanTwo"/>
<!--构造函数注入-->
<bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne">
<constructor-arg name="age" index="0" type="int" value="20"/>
<constructor-arg name="name" index="1" type="java.lang.String" value="xxx"/>
<constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
</bean>
<!--setter注入-->
<bean id="bean3" class="com.crab.spring.ioc.demo02.BeanThree">
<!-- 1 ref元素-->
<property name="beanOne" ref="bean1"></property>
<!-- 2 ref标签-->
<property name="beanTwo">
<ref bean="bean2"></ref>
</property>
<property name="name" value="xxxx"/>
</bean>
</beans>
运行测试类和结果,可见依赖注入成功
public class demo02Test {
@Test
public void test_construct() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml");
BeanOne bean1 = context.getBean("bean1", BeanOne.class);
System.out.println(bean1);
System.out.println("演示Setter注入");
BeanThree beanThree = context.getBean(BeanThree.class);
System.out.println(beanThree);
context.close();
}
}
BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24}
演示Setter注入
BeanThree{name='xxxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24, beanOne=BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24}}
依赖解决过程
- ApplicationContext是用描述所有bean的配置元数据创建和初始化的。配置元数据可以通过XML、Java代码或注释指定。
- 对于每个bean,它的依赖项都以属性、构造函数参数或静态工厂方法的参数的形式表示(如果使用静态工厂方法而不是普通构造函数的话)。当bean实际创建时,这些依赖项被提供给bean。
- 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
- 每个具有值的属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将字符串格式提供的值转换为所有内置类型,如int、long、string、boolean等。
什么是循环依赖,如何处理?
如果主要使用构造函数注入,则可能会创建一个不可解析的循环依赖场景。
例如:类A需要类B的一个实例通过构造函数注入,类B需要类A的一个实例通过构造函数注入。如果将类A和类B的bean配置为相互注入,Spring IoC容器会在运行时检测此循环引用,并抛出
BeanCurrentlyInCreationException。一个可能的解决方案是编辑一些由setter而不是构造函数配置的类的源代码。或者,避免构造函数注入,只使用setter注入。换句话说,可以使用setter注入配置循环依赖项。
与典型情况(没有循环依赖关系)不同,bean A 和 bean B 之间的循环依赖关系强制其中一个 bean 在完全初始化之前注入另一个 bean(典型的先有鸡还是先有蛋的场景)。
萝卜青菜各有所爱
选择用构造器注入还是Setter注入?
Spring官方的推荐,构造函数用于强制依赖项,将 setter 方法或配置方法用于可选依赖项。请注意,在 setter 方法上使用 @Required 注释可用于使属性成为必需的依赖项;然而,带有参数的编程验证的构造函数注入是更可取的。
Spring 团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为旁注,大量的构造函数参数是一种不好的代码气味,这意味着该类可能有太多的职责,应该重构以更好地解决适当的关注点分离。 Setter 注入应该主要只用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。 setter 注入的一个好处是 setter 方法使该类的对象可以在以后重新配置或重新注入。
有时,在处理没有源代码的第三方类时,如果第三方类没有公开任何 setter 方法,那么构造函数注入可能是 DI 的唯一可用形式。
总结
本文演示2种依赖注入的方式:构造函数注入和Setter方法注入,并对比如何选择这2种方式。下一篇继续深入依赖注入。
知识分享,转载请注明出处。学无先后,达者为先!
Spring系列4:依赖注入的2种方式的更多相关文章
- Spring:DI依赖注入的几种方式
据我所学,spring实现依赖注入(DI)的方式分为三大类:基于构造器(构造方法)的依赖注入.基于setter的依赖注入.其他方式(c命名空间.p命名空间等).其中推荐使用setter方法注入,这种注 ...
- Spring系列之依赖注入的方式
一.依赖注入方式 对于spring配置一个bean时,如果需要给该bean提供一些初始化参数,则需要通过依赖注入方式,所谓的依赖注入就是通过spring将bean所需要的一些参数传递到bean实例对象 ...
- 峰Spring4学习(2)依赖注入的几种方式
一.装配一个bean 二.依赖注入的几种方式 com.cy.entity People.java: package com.cy.entity; public class People { pri ...
- ASP.NET MVC中使用Unity进行依赖注入的三种方式
在ASP.NET MVC中使用Unity进行依赖注入的三种方式 2013-12-15 21:07 by 小白哥哥, 146 阅读, 0 评论, 收藏, 编辑 在ASP.NET MVC4中,为了在解开C ...
- SSH深度历险记(八) 剖析SSH核心原则+Spring依赖注入的三种方式
于java发育.一类程序猿必须依靠类的其他方法,它是通常new依赖类的方法,然后调用类的实例,这样的发展问题new良好的班统一管理的例子.spring提出了依赖注入的思想,即依赖类不由程 ...
- SSH深度历险(八) 剖析SSH核心原理+Spring依赖注入的三种方式
在java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依 ...
- Spring IOC 依赖注入的两种方式XML和注解
依赖注入的原理 依赖注入的方式---XML配置 依赖注入的方式---注解的方式 Spring 它的核心就是IOC和AOP.而IOC中实现Bean注入的实现方式之一就是DI(依赖注入). 一 DI的原理 ...
- Spring依赖注入的三种方式
看过几篇关于Spring依赖注入的文章,自己简单总结了一下,大概有三种方式: 1.自动装配 通过配置applicationContext.xml中的标签的default-autowire属性,或者标签 ...
- Spring注解依赖注入的三种方式的优缺点以及优先选择
当我们在使用依赖注入的时候,通常有三种方式: 1.通过构造器来注入: 2.通过setter方法来注入: 3.通过filed变量来注入: 那么他们有什么区别吗?应该选择哪种方式更好? 三种方式的区别小结 ...
随机推荐
- Nacos——注册中心
目录 1.什么是nacos 2.使用--依赖+配置文件 3.Nacos服务分级存储模型 4.服务跨集群调用问题 5.服务集群属性--配置服务集群 6. Nacos-NacosRule负载均衡 7.根据 ...
- 【LeetCode】77. Combinations 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:递归 方法二:回溯法 日期 题目地址:htt ...
- 1632 B君的连通
B国拥有n个城市,其交通系统呈树状结构,即任意两个城市存在且仅存在一条交通线将其连接.A国是B国的敌国企图秘密发射导弹打击B国的交通线,现假设每条交通线都有50%的概率被炸毁,B国希望知道在被炸毁之后 ...
- 小白自制Linux开发板(第二季 V3s篇) 一. 换个核心再来一次
1.前言 大家心心念念(个人认为)的小白自制开发板全新系列正式来了,之前我们使用全志的F1C200s芯片制作了一个小电脑,众所周知,调试很艰难,坑也很多,以至于墨云到现在还是没找到对应的补救方案,为了 ...
- Elasticsearch核心技术(五):搜索API和搜索运行机制
本文将从数据存储和搜索的角度简单分析Elasticsearch的搜索运行机制,主要涉及搜索API.搜索机制.存在问题和解决方案. 4.1 Search API Search API允许用户执行一个搜索 ...
- Xcode导入IQKeyboardManager库
下载IQKeyboardManager库 下载链接:https://github.com/hackiftekhar/IQKeyboardManager 将IQKeyboardManager文件夹拖入工 ...
- Arm64架构下编译便携Python
这段时间,我一直忙于将 Rainbond 源码构建模块移植到 Arm64/aarch64 架构中.对于 Python 项目而言,可以直接通过源代码编译成为可运行在各种容器平台之上的容器镜像.这个过程不 ...
- Mysql 设计超市经营管理系统,包括员工库存表(stock) 和 仓库表(warehouse)
互联网技术学院周测机试题(三) 一.需求分析 为进一步完善连锁超市经营管理,提高管理效率,减少管理成本,决定开发一套商品管理系统,用于日常的管理.本系统分为商品管理.员工管理.店铺管理,库存管理等功能 ...
- 表达式树扩展 动态生成表达式树插件 Sy.ExpressionBuilder。
CURD中,基础查询我感觉还是很烦人的一个浪费时间的工作,我经历过远古时代的GetAll(string name,int age),这种方式写服务的时候真的是心中一万个草泥马飞过,后面逐渐的变成了传一 ...
- SpringBoot集成jasperreport实现打印功能
1.jaspersoft studio工具下载地址 下载直通车 2.工具使用方法查看以下链接 工具使用 3.将工具编译后的.jasper文件放到SpringBoot项目的resources/templ ...