10Bean的循环依赖+反射机制

A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。
10Bean的循环依赖+反射机制_第1张图片

package com.sunsplanter.spring6.bean;

public class Husband {
    private String name;
    private Wife wife;
}
package com.sunsplanter.spring6.bean;

public class Wife {
    private String name;
    private Husband husband;
}

Spring解决循环依赖的机理十、回顾反射机制

Spring只能解决setter方法注入的单例bean之间的循环依赖。
1.set方法+两个单例Bean存在循环依赖(即ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环):可以解决。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。

Spring为什么可以解决set + singleton模式下循环依赖?“提前曝光,往后赋值”
根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。
实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。
给Bean属性赋值的时候:调用setter方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。

2.set方法+1个单例Bean和1个多例Bean存在循环依赖可以解决,同1。
3.set方法+2个多例Bean存在循环依赖无法解决。Spring只对scope为singleton的Bean提前曝光,多例Bean无法提前曝光
4.。构造方法注入:不论单例、多例,均可能创建对象失败。

测试Set注入解决两个单例Bean存在的循环依赖问题:

package com.circular_dependency;

public class Husband {
    private String name;
    private Wife wife;

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

    public String getName() {
        return name;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }
    
    // toString()方法重写时需要注意:若直接输出husband,会去调用husband的toString方法,而Husband的toString方法又要输出Wife,转而调用调用Wife的toString方法
    // 导致出现递归导致的栈内存溢出错误。输出husband.getName()。
    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}
package com.circular_dependency;

public class Wife {
    private String name;
    private Husband husband;

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

    public String getName() {
        return name;
    }

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

    // toString()方法重写时需要注意:若直接输出husband,会去调用husband的toString方法,而Husband的toString方法又要输出Wife,转而调用调用Wife的toString方法
    // 导致出现递归导致的栈内存溢出错误。输出husband.getName()。
    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

回顾反射机制

1.不使用反射机制调用方法

//用system对象调用login方法登录,成功则boolean为true
boolean success = systemService.login("admin", "admin123");

若不使用反射机制,调用一个方法,一般涉及到4个要素:
● 调用哪个对象的(systemService)
● 哪个方法(login)
● 传什么参数(“admin”, “admin123”)
● 返回什么值(success)


2.1使用反射机制调用方法
假设存在一个类:

public class SystemService {

    public void logout(){
        System.out.println("退出系统");
    }

    public boolean login(String username, String password){
        if ("admin".equals(username) && "admin123".equals(password)) {
            return true;
        }
        return false;
    }
    
    public boolean login(String password){
        if("110".equals(password)){
            return true;
        }
        return false;
    }
}

目标:获取login(String,String) 方法并调用

第一步:创建对象(四要素之首:调用哪个对象的)。首先需要获取这个类Class。

Class clazz = Class.forName("com.powernode.reflect.SystemService");
//先获取构造方法,用构造方法创建对象
Constructor<?> con = clazz.getDeclaredConstructor();
con.newInstance();

第二步:获取方法login(String,String)(四要素之一:哪个方法)。拿到Class之后,调用getDeclaredMethod()方法可以获取到方法并赋值给loginMethod。

//第二,第三个参数指定方法的参数类型
Method loginMethod = clazz.getDeclaredMethod("login", String.class, String.class);

第三步:调用方法

Object retValue = loginMethod.invoke(obj, "admin", "admin123");

在上述三步中,四要素为:
● 哪个对象:obj
● 哪个方法:loginMethod
● 传什么参数:“admin”, “admin123”
● 返回什么值:retValue

2.2使用反射机制调用set方法给属性赋值
假设有如下类:

package com.sunsplanter.reflect;

public class User {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

知道以下这几条信息:
● 类名是:com.sunsplanter.reflect.User
● 该类中有String类型的name属性和int类型的age属性。
● 另外你也知道该类的设计符合javabean规范。(也就是说属性私有化,对外提供setter和getter方法)

目标:通过反射机制给User对象的name属性赋值zhangsan,给age属性赋值20岁。

package com.sunsplanter.reflect;

import java.lang.reflect.Method;

public class UserTest {
    public static void main(String[] args) throws Exception{
        // 已知类名
        String className = "com.sunsplanter.reflect.User";
        // 已知属性名
        String propertyName = "age";

        // 创建对象
        Class<?> clazz = Class.forName(className);
        Object obj = clazz.newInstance(); 

        // 根据属性名获取setter方法名,而方法名必然符合规范,也即setAge
        //先将age全部转大写AGE,截取第一个A,再用subString获取age第一个以后的字母ge,一起拼成Age.
        String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);

        // 获取Method
        Method setMethod = clazz.getDeclaredMethod(setMethodName, int.class);

        // 调用Method
        setMethod.invoke(obj, 20);
        System.out.println(obj);
    }
}

你可能感兴趣的:(Spring6,spring,java,mybatis)