• 【深入浅出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() +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    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() +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    编写我们的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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    编写测试方法: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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    我们可以看到,当产生循环依赖,作用域为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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 当我们采用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() +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    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() +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    编写我们的配置文件 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    编写我们的测试文件 >> 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 因为我们利用构造方法注入,只有在参数赋值结束才会创建出对象,所以和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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    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);
            /*
                调用一个方法,我们需要知道四个元素:
                    第一,调用哪个对象
                    第二,调用对象哪个方法
                    第三,方法的参数列表
                    第四,方法的返回值情况
             */
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

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

    $ 利用反射机制调用方法

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

    采用上面的 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);
    
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

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

    $ 利用反射机制调用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 +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    编写我们的测试方法:

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 此处创建对象使用的是过时的方法,我们可以先获得无参构造器,再利用无参构造器创建对象
    Constructor<?> con = clazz.getDeclaredConstructor();
    Object o = con.newInstance();
    
    • 1
    • 2

    在这里插入图片描述

  • 相关阅读:
    SpringBoot的异常处理方式
    计算机网络——16概述和传输层服务
    Gitee 实战配置
    java-php-python-ssm试卷审批系统计算机毕业设计
    PPT文件不能编辑可以这样解决
    计算机毕设(附源码)JAVA-SSM旅行组团服务管理系统
    ssm+springmvc基于springboot的宠物领养系统的设计与实现_j5fk4
    Docker in docker 实现
    配置d3dx9.h
    pytorch -- torch.nn网络结构
  • 原文地址:https://blog.csdn.net/qq_61323055/article/details/127869459