【深入浅出Spring6】第五期——循环依赖和反射机制

news2025/8/12 7:42:47

一、Bean的循环依赖问题

  • 什么是循环依赖?
    • 类似于A依赖BB又依赖A,这样就构成了依赖闭环
  • 需求:我们创建两个类,彼此内置对方为私有属性,我们查看是否可以正常输出

$ singleton+ setter产生的循环依赖

编写我们的丈夫类和妻子类: Husband、Wife

package com.powernode.spring6.bean;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Husband {
    private String name;
    private Wife wife;

    public void setName(String name) {
        this.name = name;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}
package com.powernode.spring6.bean;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Wife {
    private String name;
    private Husband husband;

    public void setName(String name) {
        this.name = name;
    }

    public void setHusband(Husband husband) {
        this.husband = husband;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

编写我们的XML配置文件 spring.xml

<?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,属性相互调用,作用域采用单例
        Singleton + setter 模式的循环依赖
    -->
    <bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
        <property name="name" value="唐玄宗" />
        <property name="wife" ref="wifeBean" />
    </bean>

    <bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
        <property name="name" value="杨玉环" />
        <property name="husband" ref="husbandBean" />
    </bean>
</beans>

编写测试方法:CircularDependencyTest

@Test
    public void testCD(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        System.out.println(husbandBean);
        System.out.println(wifeBean);
    }

在这里插入图片描述
我们可以看到,当产生循环依赖,作用域为singleton的时候采用set注入不会产生任何问题

$ prototype+setter产生的循环依赖

  • 需求:我们只需要修改配置文件就可以查看测试结果
  • scope的属性值修改为 prototype
<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
	<property name="name" value="唐玄宗" />
	<property name="wife" ref="wifeBean" />
</bean>

<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
	<property name="name" value="杨玉环" />
	<property name="husband" ref="husbandBean" />
</bean>
  • 当我们采用prototype+setter的模式时,会出现BeanCreationException(正在创建中)异常
  • 因为不会再初始化上下文的时候创建实例,我们调用一个getBean的时候才会去创建,
    • 这就导致我们创建husband时,wife属性赋值要去创建新的Wife的对象,
    • 然后我们创建wife对象的时候, husband属性赋值又要去创建新的Husband的对象
      • 一直处于无限的创建对象的过程中
  • 只要在这个产生循环依赖的几个Bean中,存在一个Beanscopesingleton就可以解决问题

$ singleton+构造注入产生的循环依赖

  • 我们创建Husband类和Wife类,然后通过构造方法传递参数值,依旧使用默认的scope去测试

编写我们的 HusbandWife

package com.powernode.spring6.bean2;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Husband {
    private String name;
    private Wife wife;

    public Husband(String name, Wife wife) {
        this.name = name;
        this.wife = wife;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}
package com.powernode.spring6.bean2;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Wife {
    private String name;
    private Husband husband;

    public Wife(String name, Husband husband) {
        this.name = name;
        this.husband = husband;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

编写我们的配置文件 spring2.xml

<?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">

    <!--采用 singleton + 构造方法注入的模式
        Singleton 是在对象创建完之后曝光,然而构造方法注入是在属性赋值结束之后才算完成对象的创建
        构造注入产生的循环依赖无法解决
    -->
    <bean id="h" class="com.powernode.spring6.bean2.Husband">
        <!--通过构造方法注入-->
        <constructor-arg name="name" value="李隆基" />
        <constructor-arg index="1" ref="w" />
    </bean>
    <bean id="w" class="com.powernode.spring6.bean2.Wife">
        <constructor-arg index="0" value="杨贵妃" />
        <constructor-arg name="husband" ref="h" />
    </bean>
</beans>

编写我们的测试文件 >> Is there an unresolvable circular reference? 【产生的报错信息】

@Test
public void testCD2(){
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
	Husband husband = applicationContext.getBean("h", Husband.class);
	Wife wife = applicationContext.getBean("h", Wife.class);
	System.out.println(husband);
	System.out.println(wife);
}
  • 因为我们利用构造方法注入,只有在参数赋值结束才会创建出对象,所以和prototype的报错类似,第一个创建的Bean一直得不到满足
  • 那么Spring采用Singleton作用域是如何解决循环依赖的呢?

$ Spring 解决循环依赖的原理

  • Spring只能解决setter方法注入的单例bean之间的循环依赖
  • 采用singleton+set注入的方式,可以将“实例化Bean”和“给Bean属性赋值”这两个动作分离开去完成的,【这两步不要求在同一个时间点上完成】
  • Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中【缓存】
  • 所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题
  • 在这种模式下,Spring对Bean的管理分为两个阶段:
    • 第一个阶段:Spring容器加载的时候,实例化Bean之后,立即曝光【就是此时已经可以调用这个实例了】
    • 第二个阶段:Bean“曝光"之后,再调用set方法对属性赋值。

在这里插入图片描述

  • DefaultSingletonBeanRegistry中定义了三个Map集合 >> 分别对应一、二、三级缓存

    • 单例对象的缓存:key存储bean名称,value存储Bean对象【一级缓存】
    • 早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象【二级缓存】
    • 单例工厂缓存:key存储bean名称,value存储该Bean对应的ObjectFactory对象【三级缓存】
  • 在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光
    在这里插入图片描述

  • 大致流程就是先从一级缓存中找,找不到就去二级缓存中找,再找不到就去三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。

二、回顾反射机制

  • 动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制
  • 因为后续要手写Spring框架,所以我们需要在此处回顾一下反射机制

$ 方法四要素

  • 需求:我们通过一个案例来分析调用一个方法的四要素都包括什么

编写我们的 SomeService 类:包含三个doSome方法

package com.powernode.reflect;

/**
 * @author Bonbons
 * @version 1.0
 */
public class SomeService {
    // 我们提供三个doSome方法
    public void doSome(){
        System.out.println("无参doSome方法执行");
    }
    public String doSome(String s){
        System.out.println("一个参数doSome方法执行");
        return s;
    }
    public String doSome(String s, int n){
        System.out.println("两个参数doSome方法执行");
        return s + n;
    }
}

不采用反射机制,我们编写一个测试类调用我们的方法

package com.powernode.reflect;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Test {
    public static void main(String[] args) {
        // 不使用反射机制 >> 创建对象调用这三个方法
        SomeService someService = new SomeService();
        someService.doSome();
        String res = someService.doSome("王维");
        System.out.println(res);
        String res1 = someService.doSome("一刀", 999);
        System.out.println(res1);
        /*
            调用一个方法,我们需要知道四个元素:
                第一,调用哪个对象
                第二,调用对象哪个方法
                第三,方法的参数列表
                第四,方法的返回值情况
         */
    }
}

在这里插入图片描述

得出结论 >> 调用哪个对象的哪个方法,传什么参数,返回什么值 【四要素】

$ 利用反射机制调用方法

  • 需求:通过反射机制创建对象进而调用我们的方法

采用上面的 SomeService 类,只需要再编写一个测试类 Test2

package com.powernode.reflect;

import java.lang.reflect.Method;

/**
 * 我们利用反射机制来调用方法
 * @author Bonbons
 * @version 1.0
 */
public class Test2 {
    public static void main(String[] args) throws Exception{
        // 获取类
        Class<?> clazz = Class.forName("com.powernode.reflect.SomeService");
        /* 获取方法,通过getDeclaredMethods获取的全部方法
           我们通过 getDeclaredMethod获取的是我们指定的方法,参数为方法名和对应的参数列表
         */
        Method doSome = clazz.getDeclaredMethod("doSome", String.class, int.class);
        // 创建类的对象,此处使用的是过时的方法
        Object obj = clazz.newInstance();
        // 通过调用方法四要素来调用我们的方法
        Object retValue = doSome.invoke(obj, "一刀", 999);
        System.out.println(retValue);


    }
}

在这里插入图片描述
总结,使用反射机制的基本步骤 >> 获取类、获取方法、创建对象、调用方法

$ 利用反射机制调用set方法注入参数

  • 需求:根据给出的条件,我们调用方法传递参数,最后输出我们创建的对象,看参数是否传递成功
  • 条件:
    • 类名是:com.powernode.reflect.User
    • 该类中有String类型的name属性和int类型的age属性
    • 另外你也知道该类的设计符合javabean规范

编写我们的User

package com.powernode.reflect;

/**
 * @author Bonbons
 * @version 1.0
 */
public class User {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

编写我们的测试方法:

package com.powernode.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Test4 {
    /*
    我们要调用set的方法,已知信息如下:
        (1) 全限定类名 className
        (2) 方法名,propertyName
        (3) 方法类型为int
        
     */

    public static void main(String[] args) throws Exception{
    	// 已知类名
        String className = "com.powernode.reflect.User";
        // 已知方法名
        String propertyName = "age";
        // 获取类
        Class<?> clazz = Class.forName(className);
        // 获取set方法名
        String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
        // 根据属性名获取属性类型
        Field field = clazz.getDeclaredField(propertyName);
        // 此处的 int.class >> field.getType(),获取方法
        Method ageMethod = clazz.getDeclaredMethod(setMethodName, field.getType());
        // 创建对象
        Object o = clazz.newInstance();
        // 调用方法
        ageMethod.invoke(o, 20);
        // 输出对象
        System.out.println(o);
    }
}
  • 此处创建对象使用的是过时的方法,我们可以先获得无参构造器,再利用无参构造器创建对象
Constructor<?> con = clazz.getDeclaredConstructor();
Object o = con.newInstance();

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(八)Bean的生命周期

文章目录环境什么是Bean的生命周期为什么要知道Bean的生命周期Bean生命周期之5步Bean生命周期之7步Bean生命周期之10步三个点位详解&#xff1a;点位1点位2点位3演示程序Bean的作用域不同&#xff0c;管理方式不同自己new的对象如何让Spring管理上一篇&#xff1a;&#xff08;…

UE4 回合游戏项目 20- 添加人物被攻击的动画

在上一节&#xff08;UE4 回合游戏项目 19- 添加血量UI&#xff09;基础上继续添加人物被攻击时播放被攻击动画的功能。 效果&#xff1a;&#xff08;当玩家被攻击时&#xff0c;播放相应的受到伤害的动画&#xff09; 步骤&#xff1a; 1.打开“1lantu”&#xff0c;在事件图…

艾美捷试剂级SM-102解决方案

LNP是一种多组分系统&#xff0c;通常由可电离脂质或阳离子类脂质化合物、辅助脂质、胆固醇、保护剂聚乙二醇-脂质共轭物组成。 脂质纳米颗粒&#xff08;LNP&#xff09;是mRNA药物常用的载体。目前&#xff0c;BioNTech/辉瑞和 Moderna的mRNA疫苗都采用LNP作为运输载体&#…

SpringMVC ---- RESTful

SpringMVC ---- RESTful1. RESTful简介a>资源b>资源c>资源2. RESTful的实现3. HiddenHttpMethodFilter1. RESTful简介 REST&#xff1a;Representational State Transfer&#xff0c;表示层资源状态转移。 a>资源 资源是一种看待服务器的方式&#xff0c;即&…

ECM:敏感文档控制的秘密武器

ECM&#xff1a;敏感文档控制的秘密武器 您的企业每天都会创建和处理数百个文档。这些文件详细说明了企业流程、产品规格&#xff0c;并为其他员工和客户提供指导。 每天都有大量文档流入和流出您的组织&#xff0c;有一件事始终保持不变&#xff1a;那就是组织内部和外部的…

Docker零基础快速入门(通俗易懂)

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 Docker一、安装Docker二、配置镜像加速器三、Docker服务命令四、Docker镜像命令五、Docker容器命令六、Docker容器数据卷七、Docker部署应用…

view的context一定是Activity吗

最近在使用glide加载图片的时候出现“Caused by: java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity”&#xff0c;但明明在使用glide之前已经进行了Activity是否destroy的判断&#xff0c;为什么还会发生这个crash呢&#xff1f;注意到A…

【快速上手系列】使用idea调百度AI接口实现内容审核(鉴黄)功能

【快速上手系列】使用idea调百度AI接口实现内容审核&#xff08;鉴黄&#xff09;功能 一、文本审核 步骤 1、百度AI开放平台 登录后点击控制台 点击应用列表——点击创建应用 填写一下信息然后创建 也可以配置审核策略 策略配置页面 然后回到概览——领取免费资源&#xf…

threeJS嵌入可交互的普通页面

效果图:1.这是我将一个地图当作地面&#xff0c;外面再包一个天空盒就更好看了 2.上面的例子可能不够直观&#xff0c;下面这个例子是嵌入的bilibili官网&#xff0c;嵌入的网页内容可以正常交互 关键 关键是用到了CSS3DRenderer渲染器。CSS3DRenderer仅仅关注普通的DOM元素&a…

【计算机网络】无线局域网详解

文章目录无线局域网IEEE802.11IEEE802.11的MAC帧头有固定基础设施的无线局域网无固定基础设施无线局域网的自组织网络VLAN传统局域网的局限VLAN虚拟局域网基本概念virtual local area networkVLAN 实现&#x1f343;博主昵称&#xff1a;一拳必胜客 &#x1f338;博主寄语&…

数据增强方法汇总

数据增强1.有监督数据增强1.1 单样本数据增强augly安装augly使用方法1.2 多样本数据增强1.2.1 SMOTEpython实现1.2.2 SamplePairingpython实现1.2.3 mixuppython实现2.无监督数据增强2.1 GAN2.2 Diffunsion2.3 Autoaugmentation1.有监督数据增强 1.1 单样本数据增强 augly安装…

MongoDB(4.0.9)数据从win迁移到linux

服务器从win迁移导了linux上了&#xff0c;对应的md里面的数据也需要做全量迁移&#xff0c;在网上找了一大堆方案&#xff0c;不是缺胳膊就是少腿&#xff0c;没有一个是完整的&#xff0c;最终加以分析和整理&#xff0c;得出这套方案&#xff0c;希望对你有用 第一步&#…

Java集合框架【二容器(Collection)[Vector容器类]】

文章目录三 Vector容器类3.5 Vector容器类3.5.1 Vector的使用3.5.2 Stack容器3.5.3.1 Stack容器介绍3.5.3.2 操作栈方法Stack的使用案例三 Vector容器类 3.5 Vector容器类 Vector底层是用数组实现的&#xff0c;相关的方法都加了同步检查&#xff0c;因此“线程安全&#xff…

D. Divide and Sum(组合数学)

Problem - 1445D - Codeforces 题意: 给你一个长度为2n的数组a。考虑将数组a划分为两个子序列p和q&#xff0c;每个子序列的长度为n&#xff08;数组a的每个元素应该正好在一个子序列中&#xff1a;要么在p中&#xff0c;要么在q中&#xff09;。 让我们以非递减顺序对p进行排…

matplotlib笔记

一、安装matplotlib总是超时导致失败 鉴于公司内网服务器上直接pip install matplotlib容易超时退出的问题&#xff0c;可以采用下面的方法解决&#xff1a; 方法一&#xff1a;指定更新源 pip install -i Simple Index matplotlib3.2.2 注意选择3.2.2&#xff0c;因为最新版本…

AP22615AWU-7、SLG5NT1758V配电开关 驱动器 IC资料

AP22615配电开关具有输出过压保护 (OVP) 功能&#xff0c;设计用于USB和其他热插拔应用。该器件提供输出过压保护&#xff0c;可保护这些应用的系统。具有输出过压保护、反向电流阻断、过流、过热和短路保护功能。其他功能包括受控上升时间和欠压锁定功能。 AP22615具有可调限…

【Java篇】备战面试——你真的了解“基本数据类型”吗?

目录 基本介绍&#xff1a; 整数类型 浮点类型 布尔类型和char类型 自动类型转换 数据类型转换必须满足如下规则&#xff1a; 基本介绍&#xff1a; Java是一门强类型语言&#xff0c;这就意味着必须为每一个变量声明一种类型。Java为我们提供了八种基本类…

[附源码]计算机毕业设计JAVA归元种子销售管理系统

[附源码]计算机毕业设计JAVA归元种子销售管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

【毕业设计】大数据分析的航空公司客户价值分析 - python

文章目录0 前言1 数据分析背景2 分析策略2.1 航空公司客户价值分析的LRFMC模型2.2 数据2.3 分析模型3 开始分析3.1 数据预处理3.1.1 数据预览3.1.2 数据清洗3.2 变量构建3.3 建模分析4 数据分析结论4.1 整体结论4.2 重要保持客户4.3 重要挽留客户4.4 一般客户与低价值客户5 最后…

Cadence Allegro PCB设计88问解析(十七) 之 Allegro中焊盘的全连接和花焊盘

一个学习信号完整性仿真的layout工程师 上一篇文章和大家分享了关于铜皮shape的一些基本操作。我们进行铺铜是为了连接网络(焊盘、过孔等)&#xff0c;一般都是GND或者电源网络。Shape和走线还是不一样的&#xff0c;走线直接从焊盘或者过孔等直接拉出一根layout&#xff0c;但…