本文内容

  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. curl英文直译

    文档概述 比较表 curl手册页 常见问题 HTTP脚本编写 mk-ca-bundle 教程 curl / 文件 / 工具文档 /手册页 curl.1手册页 相关: 手动 常见问题解答 HTTP脚本 ...

  2. NAT各种模式

    https://blog.csdn.net/u011245325/article/details/9294229

  3. 可以通过外键的.id直接传值

    可以通过外键的.id直接传值 如<input type="text" name="user.department.id" value="1&qu ...

  4. Spring Boot新增一个YML配置文件,并进行加载

    我们在同级目录下增加 然后增加一个配置类 SpringBootConfiguration.java import org.springframework.beans.factory.config.Ya ...

  5. vue常用技巧-动态btn的封装

    @1.要求: 1.点击某个按钮后激活active样式,其余按钮则为normal样式 2.要满足任意个数btn(btn个数不确定) @2.思路: 1.首先,btn个数不确定则意味着必须使用v-for循环 ...

  6. 【LeetCode】1185. Day of the Week 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 计算与1971-1-1之间天数 日期 题目地址:htt ...

  7. DP? (hdu3944)

    DP? Time Limit: 10000/3000 MS (Java/Others)    Memory Limit: 128000/128000 K (Java/Others)Total Subm ...

  8. 教学日志:javaSE-java中的数据类型和运算符

    一.java中的标识符 /* 标识符的命名规范: 硬性要求: 1.必须以字母._下划线.美元符$开头 2.其它部分可以是字母.下划线"_".美元符"$"和数字的 ...

  9. Go语言核心36讲(新年彩蛋)--学习笔记

    新年彩蛋 | 完整版思考题答案 基础概念篇 Go 语言在多个工作区中查找依赖包的时候是以怎样的顺序进行的? 答:你设置的环境变量GOPATH的值决定了这个顺序.如果你在GOPATH中设置了多个工作区, ...

  10. Distillation as a Defense to Adversarial Perturbations against Deep Neural Networks

    目录 概 主要内容 算法 一些有趣的指标 鲁棒性定义 合格的抗干扰机制 Nicolas Papernot, Patrick McDaniel, Xi Wu, Somesh Jha, Ananthram ...