一、Bean的循环依赖问题
- 什么是循环依赖?
- 类似于A依赖B,B又依赖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中,存在一个Bean的scope为singleton就可以解决问题
$ singleton+构造注入产生的循环依赖
- 我们创建Husband类和Wife类,然后通过构造方法传递参数值,依旧使用默认的scope去测试
编写我们的 Husband 和 Wife 类
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();