1.循环依赖
目录
2.1 为了更好的解释使用缓存解决循环依赖问题,我们把创建bean的过程简化为以下三步:
4.4 addSingletonFactory方法 - 源码截图
4.6 A和B相互依赖,在spring(三级缓存)中的创建流程图
个人学习记录,希望对大家有帮助,有问题畅所欲言
1.循环依赖
1.1 概念
循环依赖:简单来说是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用
1.2 循环依赖的N种场景
1.2.1 单例的setter注入(singleton) -- 可解决的
后面会重点讲解,spring三级缓存如何解决此场景的循环依赖
1.2.2 多例的setter注入(prototype) -- 不可解决的
a. @Scope注解,定义bean在springIoc容器中的作用域
b. prototype,原型模式,每次从容器中获取bean时,会新建一个实例,由请求方负责后继生命周期的管理,包括 销毁
c. 不可解决原因:每次都会创建新的对象,不会在缓存中存储,交给请求方后就会失去对此对象的索引
1.2.3 构造器注入 -- 不可解决
原因:创建实例完成之前,就需要去获取依赖的对象,此时半成品的bean未加入到缓存中,导致依赖无限循环下去
创建流程
2.模拟一级缓存
2.1 为了更好的解释使用缓存解决循环依赖问题,我们把创建bean的过程简化为以下三步:
a.实例化:创建出一个属性未赋值的对象
b.填充属性:给对象的属性赋值
c.初始化:执行init-Method
2.2 创建流程(A和B相互依赖)
创建A
1. 实例化A,将未填充属性的 半成品的a对象放入缓存中(a不完整,b=null)
2. 填充属性b
创建B:
3. 实例化B,将未填充属性的 半成品的b对象放入缓存中(b不完整,a=null)
4. 填充属性a,从缓存中获取了a(a不完整,b=null) 返回b
5. A中填充b
A创建完成
2.3 创建流程图:
2.4 代码实现
InstanceA
//创建A类,属性依赖B
public class InstanceA {
@Autowired
InstanceB instanceB;
public InstanceA(){
}
public void say(){
System.out.println("hello world");
}
}
InstanceB
//创建B类,属性依赖A
public class InstanceB {
@Autowired
InstanceA instanceA;
public InstanceB(){
}
}
模拟一级缓存的类:One
/**
* 通过一个缓存,尝试解决循环依赖问题
* BeanDefinition 用于保存 Bean 的相关信息,包括属性、构造方法参数、依赖的 Bean 名称及是否单例、延迟加载等
* 它是实例化 Bean 的原材料,Spring 就是根据 BeanDefinition 中的信息实例化 Bean
*/
public class One {
private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
public static void loadBeanDefinition(){
BeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
BeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
beanDefinitionMap.put("instanceA",aBeanDefinition);
beanDefinitionMap.put("instanceB",bBeanDefinition);
}
public static void main(String[] args) throws Exception{
loadBeanDefinition();
InstanceA instanceA = (InstanceA)getBean("instanceA");
instanceA.say();
}
//一级缓存
public static Map<String,Object> singletonObjects = new ConcurrentHashMap<>();
private static Object getBean(String beanName) throws InstantiationException, IllegalAccessException {
//从缓存中获取
Object singleton = getSingleton(beanName);
if(singleton != null){
return singleton;
}
//实例化 通过反射创建对象
RootBeanDefinition beanDefinition = (RootBeanDefinition)beanDefinitionMap.get(beanName);
Class<?> beanClass = beanDefinition.getBeanClass();
Object instance = beanClass.newInstance();
//添加到一级缓存中 对象此时没有属性赋值 半成品的bean
singletonObjects.put(beanName,instance);
//填充属性
Field[] declaredFields = beanClass.getDeclaredFields();
for(Field declaredField : declaredFields){
Autowired annotation = declaredField.getAnnotation(Autowired.class);
//如果属性被Autowired注解修饰
if(annotation != null){
declaredField.setAccessible(true);
//获取属性名
String name = declaredField.getName();
//创建需要依赖的属性对象
Object fieldObject = getBean(name);
declaredField.set(instance,fieldObject);
}
}
//初始化 init-method
return instance;
}
private static Object getSingleton(String beanName) {
if(singletonObjects.containsKey(beanName)){
return singletonObjects.get(beanName);
}
return null;
}
}
2.5 发现问题
如何保证创建出的完整的bean是单例的???
解决:再用一个map用来缓存完整的bean,beanName作为key值,保证每一个名字可以获取到同一个实例bean
插曲:单例bean和单例模式的区别
①单例bean,是指spring容器中bean的一种作用域,一个名字对应一个实例bean
②单例模式是设计模式的一种,一个类只有一个私有的实例,对外提供了一个公共的访问入口
③区别: 单例bean,一个类可以对应多个实例 单例模式,一个类只有一个实例
3.模拟二级缓存
3.1 代码实现
模拟二级缓存的类:Two
/**
* 模拟二级缓存
*/
public class Two {
private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
public static void loadBeanDefinition(){
BeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
BeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
beanDefinitionMap.put("instanceA",aBeanDefinition);
beanDefinitionMap.put("instanceB",bBeanDefinition);
}
public static void main(String[] args) throws Exception{
loadBeanDefinition();
InstanceA instanceA = (InstanceA)getBean("instanceA");
instanceA.say();
}
//一级缓存
public static Map<String,Object> singletonObjects = new ConcurrentHashMap<>();
//二级缓存
public static Map<String,Object> earlySingletonObjects = new ConcurrentHashMap<>();
private static Object getBean(String beanName) throws InstantiationException, IllegalAccessException {
//出口
Object singleton = getSingleton(beanName);
if(singleton != null){
return singleton;
}
//1.实例化 通过反射创建对象
RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
Class<?> beanClass = beanDefinition.getBeanClass();
Object instance = beanClass.newInstance();
earlySingletonObjects.put(beanName,instance);
//2.属性赋值
Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields){
Autowired annotation = declaredField.getAnnotation(Autowired.class);
if(annotation != null){
declaredField.setAccessible(true);
String name = declaredField.getName();
Object fieldObject = getBean(name);
declaredField.set(instance,fieldObject);
}
}
//3.初始化 init-method
//添加到一级缓存中
singletonObjects.put(beanName,instance);
//判断二级缓存中有没有该名字的半成品,如果有,就移除
if(earlySingletonObjects.containsKey(beanName)){
earlySingletonObjects.remove(beanName);
}
return instance;
}
private static Object getSingleton(String beanName) {
//判断一级缓存有没有
if(singletonObjects.containsKey(beanName)){
return singletonObjects.get(beanName);
//判断二级缓存有没有
}else if(earlySingletonObjects.containsKey(beanName)){
return earlySingletonObjects.get(beanName);
}else{
return null;
}
}
}
3.2 发现问题
如果被创建的类需要被增强,即需要进行AOP,获取到一个代理对象,此时只靠 二级缓存是无法满足的
解决:
①用三级缓存map,存储ObjectFactory
②进行AOP的三个条件,原始对象(实例化之后的对象)、beanName、BeanDefinition
③三个参数以lambda表达式的方式存储在ObjectFactory中,通过getObject()方法获取 需要的实例(方法内会判断是否需要AOP,需要的话就创建代理对象,不需要的话就返回 原始对象,即半成品的bean)
3.3 ObjectFactory -- 对象工厂
//三级缓存 /** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
通过下图,我们可以看出,ObjectFactory对象传入的是一个lambda表达式,里面有三个参数,分别是:beanName(实例名)、mbd(BeanDefinition)、bean(未属性赋值的bean)
4.spring三级缓存
4.1 bean的创建过程(丰富之前的创建流程)
class(UserService) --> 推断构造方法 --> 实例化 --> 对象 --> 属性填充 --> afterPropertiesSet(初始化 调用initMethod) --> AOP --> 代理对象 --> bean
4.2 三级缓存存储结构 - 源码
DefaultSingletonBeanRegistry.java
//一级缓存
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三级缓存
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//二级缓存
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
源码截图
4.3 getSingleton方法 - 源码截图
spring源码的bean包中: DefaultSingletonBeanRegistry.java
① isSingletonCurrentlyInCreation() 判断当前单例bean是否正在创建中
② allowEarlyReference: 是否允许从singletonFactories中 通过getObject拿到对象
4.4 addSingletonFactory方法 - 源码截图
spring源码的bean包中: DefaultSingletonBeanRegistry.java
在执行完构造方法之后,完成实例化,创建出 半成品的bean之后,将lambda表达式缓存在三级缓存中,此时,调用此方法
4.5 三级缓存对应的缓存内容
4.6 A和B相互依赖,在spring(三级缓存)中的创建流程图
4.7 思考
4.7.1 上图中,A创建的第8步是为什么,是否可以忽略??
答:
①第8步会判断是否进行了提前AOP,因为出现循环依赖的话,AOP生成代理对象 会比正常创建过程提前
②如果省略第8步,即使二级缓存中是A的代理对象,保存在一级缓存中的也只能 是完整的原始对象
③归根结底,对象创建完之后,一定要从缓存中拿出,放在一级缓存中,然后 移除二、三级缓存
4.7.2 为什么一级缓存是ConcurrentHashMap,二、三级缓存是HashMap??
答:
①一级缓存为了让外界获取被spring容器管理的bean,可能会有多个线程同时获取, 所以要用ConcurrentHashMap来保证线程安全
②二、三级缓存的put和remove操作绑定在一起,并且被synchronized同步锁修饰, 已经保证线程安全,所以要用速度更快、线程不安全的HashMap