• 4、IOC 之Bean的依赖关系


    4、IOC 之Bean的依赖关系

    4.1、依赖注入(DI)

    依赖关系注入 (DI) 是一个过程,其中对象仅通过构造函数参数、工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖关系(即,它们与之一起工作的其他对象)。然后,容器在创建 Bean 时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转

    DI 有两种主要变体:基于构造函数的依赖关系注入基于 Setter 的依赖关系注入

    ① 基于构造函数的依赖关系注入

    1、构造函数参数解析

    基于构造函数的 DI 是通过容器调用具有许多参数的构造函数来完成的,每个参数表示一个依赖项。调用具有特定参数的工厂方法来构造 Bean 几乎是等效的。下面的示例演示一个只能使用构造函数注入进行依赖关系注入的类

    Bean类:

    package hom.wang.vo;
    import lombok.Data;
    @Data
    public class ExampleBean {
        private String name;
        private int age;
        private int money;
        private BeanA beanA;
        private BeanB beanB;
    
        public ExampleBean(String name, int age, int money, BeanA beanA, BeanB beanB) {
            this.name = name;
            this.age = age;
            this.money = money;
            this.beanA = beanA;
            this.beanB = beanB;
        }
    }
    
    // 同包下BeanA和BeanB
    public class BeanA {}
    public class BeanB {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    xml定义Bean:

    构造函数参数解析匹配通过使用参数的类型解析。如果 Bean 定义的构造函数参数中不存在潜在的歧义,则在 Bean 定义中定义构造函数参数的顺序就是在实例化 Bean 时将这些参数提供给相应构造函数的顺序。

    由于 BeanA 和 BeanB类不通过继承相关,则不存在潜在的歧义。因此,以下配置工作正常,并且不需要在元素中显式指定构造函数参数索引或类型

    <beans>
        <bean id="beanA" class="hom.wang.vo.BeanA"/>
        <bean id="beanB" class="hom.wang.vo.BeanB"/>
    
        <bean id="exampleBean" class="hom.wang.vo.ExampleBean">
            <constructor-arg value="张三"/>
            <constructor-arg value="20"/>
            <constructor-arg value="30"/>
            <constructor-arg ref="beanA"/>
            <constructor-arg ref="beanB"/>
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    2、构造函数参数类型匹配

    通过 type 指定参数类型,避免相同类型的参数多义性(然并卵,不看类实现你依然不知道每个属性是神马含义)

    xml定义Bean(沿用上面①的Bean类):

    <beans>
        <bean id="beanA" class="hom.wang.vo.BeanA"/>
        <bean id="beanB" class="hom.wang.vo.BeanB"/>
    
        <bean id="exampleBean" class="hom.wang.vo.ExampleBean">
            <constructor-arg type="java.lang.String" value="张三"/>
            <constructor-arg type="int" value="20"/>
            <constructor-arg type="int" value="30"/>
            <constructor-arg type="hom.wang.vo.BeanA" ref="beanA"/>
            <constructor-arg type="hom.wang.vo.BeanB" ref="beanB"/>
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    3、构造函数参数索引

    通过 index 显式指定构造函数参数的索引(索引从 0 开始)

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

    (然并卵,不看类具体定义你依然不知道每个属性是神马含义)

    xml定义Bean(沿用上面①的Bean类):

    <beans>
        <bean id="beanA" class="hom.wang.vo.BeanA"/>
        <bean id="beanB" class="hom.wang.vo.BeanB"/>
    
        <bean id="exampleBean" class="hom.wang.vo.ExampleBean">
            <constructor-arg index="0" value="张三"/>
            <constructor-arg index="1" value="20"/>
            <constructor-arg index="2" value="30"/>
            <constructor-arg index="3" ref="beanA"/>
            <constructor-arg index="4" ref="beanB"/>
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    4、构造函数参数名称

    通过 name 指定参数名称来消除歧义

    xml定义Bean(沿用上面①的Bean类):

    <beans>
        <bean id="beanA" class="hom.wang.vo.BeanA"/>
        <bean id="beanB" class="hom.wang.vo.BeanB"/>
    
        <bean id="exampleBean" class="hom.wang.vo.ExampleBean">
                <constructor-arg name="name" value="张三"/>
                <constructor-arg name="age" value="20"/>
                <constructor-arg name="money" value="30"/>
                <constructor-arg name="beanA" ref="beanA"/>
                <constructor-arg name="beanB" ref="beanB"/>
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    **【 MISUNDERSTAND:摘自官网,小白也不清楚,先记录一波】**请记住,要开箱即用,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名称。如果不能或不想使用调试标志编译代码,则可以使用 JDK提供的注解 @ConstructorProperties 显式命名构造函数参数。

    @ConstructorProperties({"name", "age", "money", "beanA", "beanB"})
    public ExampleBean(String name, int age, int money, BeanA beanA, BeanB beanB) {
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4

    ② 基于 setter 的依赖关系注入

    基于 Setter 的 DI 是在调用无参数构造函数或无参数工厂方法来实例化 Bean 之后,通过容器调用 Bean 上的 setter 方法来完成的(用 指定属性)。

    xml定义Bean(沿用上面①的Bean类):

    <beans>
        <bean id="beanA" class="hom.wang.vo.BeanA"/>
        <bean id="beanB" class="hom.wang.vo.BeanB"/>
    
        <bean id="exampleBean" class="hom.wang.vo.ExampleBean">
            <property name="name" value="张三"/>
            <property name="age" value="20"/>
            <property name="money" value="30"/>
            <property name="beanA" ref="beanA"/>
            <property name="beanB" ref="beanB"/>
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    基于构造函数还是基于 setter 的 DI?

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

    【 MISUNDERSTAND:摘自官网,编程验证?嘛意思?也许是@NotNull这种】

    依赖关系解析过程(摘自官网可略过)

    容器按如下方式执行 Bean 依赖关系解析:

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

    Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,不会设置bean属性本身。创建容器时,将创建单例作用域并设置为预实例化(默认)的bean。作用域是在Bean作用域中定义的。否则,仅在请求时创建bean。

    循环依赖关系

    如果主要使用构造函数注入,则可以创建无法解析的循环依赖关系方案。

    例如:类 A 需要通过构造函数注入的类 B 的实例,而类 B 需要通过构造函数注入的类 A 的实例。如果将 Bean 配置为将类 A 和 B 相互注入,则 Spring IoC 容器会在运行时检测到此循环引用,并引发一个 BeanCurrentlyInCreationException

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

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

    4.2、依赖关系和配置详情

    可直接参考 官方文档

    4.3、使用 depends-on

    通常使用基于XML的配置元数据中的 ref 属性来实现简单的依赖项,然而,有时bean之间的依赖关系不那么直接。例如,需要触发类中的静态初始值设定项,例如数据库驱动程序注册。该属性可以显式强制一个或多个bean在初始化使用该元素的bean之前进行初始化。下面的示例使用 depends-on 属性来表示对单个bean的依赖关系:

    <bean id="beanOne" class="ExampleBean" depends-on="manager"/>
    <bean id="manager" class="ManagerBean" />
    
    • 1
    • 2

    要表达对多个 bean的依赖关系,请提供 bean名称列表作为属性值(逗号、空格和分号是有效的分隔符)

    <bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
        <property name="manager" ref="manager" />
    bean>
    
    <bean id="manager" class="ManagerBean" />
    <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    该属性既可以指定初始化时间依赖,也可以指定相应的销毁时间依赖。定义与给定 bean的关系的依赖 bean在给定 bean本身被销毁之前首先被销毁。因此,它还可以控制关闭顺序。

    4.4、延迟初始化的 Bean lazy-init

    ( Lazy-initialized)延迟初始化 bean 告诉 IoC容器在第一次请求时创建bean实例,而不是在启动时创建。在XML中使用 lazy-init="true" 来指定。

    <bean id="beanOne" class="ExampleBean" lazy-init="true"/>
    
    • 1

    4.5、自动装配 autowire

    当使用基于XML的配置元数据时,可以使用元素的 autowire 属性为bean定义指定自动装配。

    自动装配是Spring满足bean依赖的一种方式!
    Spring会在上下文中自动寻找,并自动给bean装配属性!
    在Spring中有三种装配方式:

    1. 在xml中显式配置
    2. 在java中显式配置
    3. 隐式的自动装配bean(重要)
    模式解释
    no(默认值)无自动装配。Bean ref 引用必须由元素定义。对于大型部署,不建议更改默认设置,因为显式指定协作者可提供更好的控制和清晰度。在某种程度上,它记录了系统的结构
    byName按属性名称自动装配。Spring寻找与需要自动装配的属性同名的Bean。例如,如果一个Bean定义被设置为按名称自动装配,并且它包含一个属性master(即,它有一个方法 setMaster(..)),Spring会查找一个名为master 的bean的定义,并使用它来设置属性
    byType如果容器中只存在一个属性类型的 Bean,则允许此自动装配。如果存在多个,则会引发致命异常,这表示您不能对该 Bean 使用byType自动装配。如果没有匹配的 bean,则不会发生任何操作(未设置该属性)
    constructor类似于 byType 但适用于构造函数参数。如果容器中没有构造函数参数类型的一个 Bean,则会引发致命错误
    ① 手动配置

    同包下三个类:人有两个动物

    package hom.wang.vo;
    import lombok.Data;
    @Data
    public class People {
        private Dog dog;
        private Cat cat;
        private String name;
    }
    
    public class Dog {
        public void say(){
            System.out.println("汪汪汪");
        }
    }
    
    public class Cat {
        public void say(){
            System.out.println("喵喵喵");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    xml配置:

    <bean id="dog" class="hom.wang.vo.Dog"/>
    <bean id="cat" class="hom.wang.vo.Cat"/>
    
    <bean id="people" class="hom.wang.vo.People">
        <property name="dog" ref="dog"/>
        <property name="cat" ref="cat"/>
        <property name="name" value="小白"/>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    ② 自动装配 autowire="byName"

    注意!!!容器内必须存在按照规范命名的 bean的 id(对应setter的属性),否则自动装配失败

    xml配置:

    <bean id="dog" class="hom.wang.vo.Dog"/>
    <bean id="cat" class="hom.wang.vo.Cat"/>
    
    <bean id="people" class="hom.wang.vo.People" autowire="byName">
        <property name="name" value="小白"/>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    ③ 自动装配 autowire="byType"

    注意!!!容器内必须存在对应类型的 bean的 class(对应setter属性的类型)且全容器此类型bean唯一,否则自动装配失败

    xml配置:

    <bean id="dog" class="hom.wang.vo.Dog"/>
    <bean id="cat" class="hom.wang.vo.Cat"/>
    
    <bean id="people" class="hom.wang.vo.People" autowire="byType">
        <property name="name" value="小白"/>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    自动装配的局限性和缺点:

    • propertyconstructor-arg设置中的显式依赖项始终覆盖自动关联。不能自动关联简单属性,如基本类型、StringsClasses(以及此类简单属性的数组)。此限制是设计使然。

    • 自动装配不如显式装配精确。尽管如前所述,Spring谨慎地避免了猜测,以免出现可能产生意外结果的歧义。Spring托管对象之间的关系不再明确记录。

    • 配线信息可能无法用于从Spring容器生成文档的工具。

    • 容器中的多个Bean 定义可能与要自动连接的setter方法或构造函数参数指定的类型相匹配。对于数组、集合或Map实例,这不一定是问题。然而,对于期望单个值的依赖项,这种模糊性不是任意解决的。如果没有唯一的bean定义可用,则抛出异常。

      • 放弃自动关联,而选择显式关联

      • 通过将 Bean 定义的属性设置为autowire-candidate="false" 来避免自动关联

      • 通过将单个 Bean 定义的元素的primary属性设置为true 来指定其为主要候选项。

      • 实现基于注释的配置可用的更细粒度的控制,如基于注释的容器配置中所述。

    从自动装配中排除 Bean:

    基于每个 Bean,从自动装配中排除 Bean。将 元素的autowire-candidate属性设置为false。容器使该特定 Bean 定义对自动装配基础结构不可用(包括注释样式配置,如@Autowired)。

    该属性设计为仅影响基于类型的自动装配。它不会影响按名称排列的显式引用,即使指定的 Bean 未标记为自动装配候选项,这些引用也会被解析。因此,如果名称匹配,则按名称自动装配仍会注入 Bean。

    (提前记录)使用注解实现自动装配

    jdk1.5支持的注解,Spring2.5就支持注解了(此小节为提前透点,更多注解相关内容可参阅后续专门开设的注解章节)

    使用注解须知:

    1. 导入约束:context约束
    2. 配置注解的支持:
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                    https://www.springframework.org/schema/beans/spring-beans.xsd
                    http://www.springframework.org/schema/context
                    https://www.springframework.org/schema/context/spring-context.xsd">
          
        <context:annotation-config/>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    @Autowired:
    直接在属性上使用即可!也可以在set方式上使用!

    public class People { 
    	@Autowired
        private Cat cat;
        
        @Autowired
        private Dog dog;
        
        /* 也可以在set方式上使用!
        @Autowired
        public void setCat(Cat cat) {
            this.cat = cat;
        } */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 默认按属性类型[byType]自动注入

    • 如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解@Autowired完成的时候 ,可以使用@Qualifier(“xxx”)去配合@Autowired的使用,即既无法通过byName又无法通过byType时,指定一个id的bean对象注入

      public class People {
      	@Autowired
          @Qualifier("cat222")
          private Cat cat;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      @Resource注解

      @Resource(name=“userDao”, type=UserDao.class) 可以 byType,也可以 byName
      @Resource 默认先 byName,找不到再 byType
      [ 不带属性类似@Autowired,带属性类似@Autowired+@Qualifier组合使用(name和type使用一个即可精准定位) ]

      public class People {
          @Resource(name="cat1111")
          private Cat cat;
          
          @Resource
          private Dog dog;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      @Autowired@Resource区别:

    • 都是用来自动装配的,都可以写在字段setter方法上

    • @Autowired 只通过 byType 实现,默认情况下必须要求依赖对象存在

      • 如果要允许null值,可以设置它的required属性false。如果想使用名称装配可以**结合@Qualifier注解**进行使用
    • @Resource 默认通过 byName 方式实现(可以通过name属性进行指定),如果找不到,则通过 byType 实现

      • 需要注意的是,如果name属性一旦指定,就只会按照名称进行装配
    • @Autowired 是Spring提供的注解,@Resource 是JDK提供的注解

    4.6、方法注入

    可直接参考 官方文档

  • 相关阅读:
    移动端click事件、touch事件、tap事件的区别
    [LeetCode][LCR149]彩灯装饰记录 I——二叉树的层序遍历
    用Excel制作甘特图跟踪项目进度(附绘制教程)
    面向海洋观监测传感网的移动终端位置隐私保护研究
    Vue 使用原生 js 实现锚点定位到指定位置
    入门力扣自学笔记111 C++ (题目编号622)
    4-19 算法思路总结
    接口自动化测试的概述及流程梳理~
    30岁之前什么新技术我都学,30岁之后什么新技术我都不学。
    深眸科技自研AI视觉分拣系统,实现物流行业无序分拣场景智慧应用
  • 原文地址:https://blog.csdn.net/qq_30769437/article/details/126744837