本文内容

  1. 基于构造器的依赖注入
  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种方法

  1. 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>
  2. 使用 @ConstructorProperties JDK 注释显式命名构造函数参数

        /**
    * 构造函数,用于依赖注入,定义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依赖BeanOneBeanTwo并提供了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中的refname来设置属性引用或是属性值

<?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种方式。下一篇继续深入依赖注入。

本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo02

知识分享,转载请注明出处。学无先后,达者为先!

Spring系列4:依赖注入的2种方式的更多相关文章

  1. Spring:DI依赖注入的几种方式

    据我所学,spring实现依赖注入(DI)的方式分为三大类:基于构造器(构造方法)的依赖注入.基于setter的依赖注入.其他方式(c命名空间.p命名空间等).其中推荐使用setter方法注入,这种注 ...

  2. Spring系列之依赖注入的方式

    一.依赖注入方式 对于spring配置一个bean时,如果需要给该bean提供一些初始化参数,则需要通过依赖注入方式,所谓的依赖注入就是通过spring将bean所需要的一些参数传递到bean实例对象 ...

  3. 峰Spring4学习(2)依赖注入的几种方式

    一.装配一个bean 二.依赖注入的几种方式 com.cy.entity   People.java: package com.cy.entity; public class People { pri ...

  4. ASP.NET MVC中使用Unity进行依赖注入的三种方式

    在ASP.NET MVC中使用Unity进行依赖注入的三种方式 2013-12-15 21:07 by 小白哥哥, 146 阅读, 0 评论, 收藏, 编辑 在ASP.NET MVC4中,为了在解开C ...

  5. SSH深度历险记(八) 剖析SSH核心原则+Spring依赖注入的三种方式

           于java发育.一类程序猿必须依靠类的其他方法,它是通常new依赖类的方法,然后调用类的实例,这样的发展问题new良好的班统一管理的例子.spring提出了依赖注入的思想,即依赖类不由程 ...

  6. SSH深度历险(八) 剖析SSH核心原理+Spring依赖注入的三种方式

           在java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依 ...

  7. Spring IOC 依赖注入的两种方式XML和注解

    依赖注入的原理 依赖注入的方式---XML配置 依赖注入的方式---注解的方式 Spring 它的核心就是IOC和AOP.而IOC中实现Bean注入的实现方式之一就是DI(依赖注入). 一 DI的原理 ...

  8. Spring依赖注入的三种方式

    看过几篇关于Spring依赖注入的文章,自己简单总结了一下,大概有三种方式: 1.自动装配 通过配置applicationContext.xml中的标签的default-autowire属性,或者标签 ...

  9. Spring注解依赖注入的三种方式的优缺点以及优先选择

    当我们在使用依赖注入的时候,通常有三种方式: 1.通过构造器来注入: 2.通过setter方法来注入: 3.通过filed变量来注入: 那么他们有什么区别吗?应该选择哪种方式更好? 三种方式的区别小结 ...

随机推荐

  1. mongodb 64位操作系统下载地址

    下载地址:https://www.mongodb.org/dl/win32/x86_64

  2. js中字符串和数组的常用转换处理方法

    1.split("分割条件(正则表达式或者字符)") 字符串 ==> 数组 默认返回数组 (1) 将单词分割为字符 "hello".split(" ...

  3. 【LeetCode】1111. Maximum Nesting Depth of Two Valid Parentheses Strings 有效括号的嵌套深度

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目讲解 划分规则讲解 返回结果讲解 解题方法 代码 日期 题目地址:ht ...

  4. 【LeetCode】617. Merge Two Binary Trees 解题报告

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcod ...

  5. 【Java笔记】Java使用mysql包注意

    注意 安装的mysql5.x版本对应 5.x版本的驱动包 安装的mysql8.x版本对应 8.x版本的驱动包 如果安装的MySQL版本和驱动包版本不符合,则Java的连接不了数据库

  6. Max-Mahalanobis Linear Discriminant Analysis Networks

    目录 概 主要内容 Pang T, Du C, Zhu J, et al. Max-Mahalanobis Linear Discriminant Analysis Networks[C]. inte ...

  7. linux系统安装java

    1.下载Java压缩包 *.gz 2.解压 3.修改Linux配置文件,配置Java环境变量 4.使用命令source /etc/profile让修改生效 转载 https://www.cnblogs ...

  8. 编写Java程序_定义两个方法,实现奇数偶数的判断,并计算和(有参数有返回值方法)

    需求说明: 定义两个方法,在控制台输入一个数字,这两个方法可以求出1到该数字之间所有偶数之和.奇数之和,并将对应结果和返回.在main方法中调用该方法,并在控制台打印出结果.(有参数有返回值方法) 运 ...

  9. JavaScript交互式网页设计 • 【第5章 JavaScript对象】

    全部章节   >>>> 本章目录 5.1 Object 对象和 Date 对象 5.1.1 JavaScript 的内部对象 5.1.2 Object对象 5.1.3 Date ...

  10. css 文本基础 实战 小米官方卡片案例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...