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

一、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();

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bow.贾斯汀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值