本次目标
1. 手写spring循环依赖的整个过程
2. spring怎么解决循环依赖
3. 为什么要二级缓存和三级缓存
4. spring有没有解决构造函数的循环依赖
5. spring有没有解决多例下的循环依赖.
一. 什么是循环依赖?
如下图所示:
A类依赖了B类, 同时B类有依赖了A类. 这就是循环依赖, 形成了一个闭环
如上图: A依赖了B, B同时依赖了A和C , C依赖了A. 这也是循环依赖. , 形成了一个闭环
那么, 如果出现循环依赖, spring是如何解决循环依赖问题的呢?
二. 模拟循环依赖
2.1 复现循环依赖
我们定义三个类:
1. 新增类InstanceA
package com.lxl.www.circulardependencies; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope("prototype") public class InstanceA { @Autowired private InstanceB instanceB; public InstanceA() { System.out.println("调用 instanceA的构造函数"); } public InstanceA(InstanceB instanceB) { this.instanceB = instanceB; } public void say(){ System.out.println( "I am A"); } public InstanceB getInstanceB() { return instanceB; } public void setInstanceB(InstanceB instanceB) { this.instanceB = instanceB; } }
这是InstanceA, 里面引用了InstanceB.
2. 新增类instanceB
package com.lxl.www.circulardependencies; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope("prototype") public class InstanceB { @Autowired private InstanceA instanceA; public InstanceB() { System.out.println("调用 instanceB的构造函数"); } public InstanceA getInstanceA() { return instanceA; } public void setInstanceA(InstanceA instanceA) { this.instanceA = instanceA; } }
这是InstanceB, 在里面有引用了InstanceA
3:模拟spring是如何创建Bean的
这个在前面已经说过了, 首先会加载配置类的后置处理器, 将其解析后放入到beanDefinitionMap中. 然后加载配置类, 也将其解析后放入beanDefinitionMap中. 最后解析配置类. 我们这里直接简化掉前两步, 将两个类放入beanDefinitionMap中. 主要模拟第三步解析配置类. 在解析的过程中, 获取bean的时候会出现循环依赖的问题循环依赖.
第一步: 将两个类放入到beanDefinitionMap中
public class MainStart { private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); /** * 读取bean定义, 当然在spring中肯定是根据配置 动态扫描注册的 * * InstanceA和InstanceB都有注解@Component, 所以, 在spring扫描读取配置类的时候, 会把他们两个扫描到BeanDefinitionMap中. * 这里, 我们省略这一步, 直接将instanceA和instanceB放到BeanDefinitionMap中. */ public static void loadBeanDefinitions(){ RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class); RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class); beanDefinitionMap.put("instanceA", aBeanDefinition); beanDefinitionMap.put("instanceB", bBeanDefinition); } public static void main(String[] args) throws Exception { // 第一步: 扫描配置类, 读取bean定义 loadBeanDefinitions(); ...... }
上面的代码结构很简单, 再看一下注释应该就能明白了. 这里就是模拟spring将配置类解析放入到beanDefinitionMap的过程.
第二步: 循环创建bean
首先,我们已经知道, 创建bean一共有三个步骤: 实例化, 属性赋值, 初始化.
而在属性赋值的时候, 会判断是否引用了其他的Bean, 如果引用了, 那么需要构建此Bean. 下面来看一下代码
/** * 获取bean, 根据beanName获取 */ public static Object getBean(String beanName) throws Exception {/** * 第一步: 实例化 * 我们这里是模拟, 采用反射的方式进行实例化. 调用的也是最简单的无参构造函数 */ RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName); Class<?> beanClass = beanDefinition.getBeanClass(); // 调用无参的构造函数进行实例化 Object instanceBean = beanClass.newInstance(); /** * 第二步: 属性赋值 * instanceA这类类里面有一个属性, InstanceB. 所以, 先拿到 instanceB, 然后在判断属性头上有没有Autowired注解. * 注意: 这里我们只是判断有没有Autowired注解. spring中还会判断有没有@Resource注解. @Resource注解还有两种方式, 一种是name, 一种是type */ Field[] declaredFields = beanClass.getDeclaredFields(); for (Field declaredField: declaredFields) { // 判断每一个属性是否有@Autowired注解 Autowired annotation = declaredField.getAnnotation(Autowired.class); if (annotation != null) { // 设置这个属性是可访问的 declaredField.setAccessible(true); // 那么这个时候还要构建这个属性的bean. /* * 获取属性的名字 * 真实情况, spring这里会判断, 是根据名字, 还是类型, 还是构造函数来获取类. * 我们这里模拟, 所以简单一些, 直接根据名字获取. */ String name = declaredField.getName(); /** * 这样, 在这里我们就拿到了 instanceB 的 bean */ Object fileObject = getBean(name); // 为属性设置类型 declaredField.set(instanceBean, fileObject); } } /** * 第三步: 初始化 * 初始化就是设置类的init-method.这个可以设置也可以不设置. 我们这里就不设置了 */ return instanceBean; }
我们看到如上代码.
第一步: 实例化: 使用反射的方式, 根据beanName查找构建一个实例bean.
第二步: 属性赋值: 判断属性中是否有@Autowired属性, 如果有这个属性, 那么需要构建bean. 我们发现在为InstanceA赋值的时候, 里面引用了InstanceB, 所以去创建InstanceB, 而创建InstanceB的时候, 发现里面又有InstanceA, 于是又去创建A. 然后以此类推,继续判断. 就形成了死循环. 无法走出这个环. 这就是循环依赖
第三步: 初始化: 调用init-method, 这个方法不是必须有, 所以,我们这里不模拟了
看看如下图所示
红色部分就形成了循环依赖.
4: 增加一级缓存, 解决循环依赖的问题.
我们知道上面进行了循环依赖了. 其实, 我们的目标很简单, 如果一个类创建过了, 那么就请不要在创建了.
所以, 我们增加一级缓存
// 一级缓存 private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); /** * 获取bean, 根据beanName获取 */ public static Object getBean(String beanName) throws Exception { // 增加一个出口. 判断实体类是否已经被加载过了 Object singleton = getSingleton(beanName); if (singleton != null) { return singleton; } /** * 第一步: 实例化 * 我们这里是模拟, 采用反射的方式进行实例化. 调用的也是最简单的无参构造函数 */ RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName); Class<?> beanClass = beanDefinition.getBeanClass(); // 调用无参的构造函数进行实例化 Object instanceBean = beanClass.newInstance(); /** * 第二步: 放入到一级缓存 */ singletonObjects.put(beanName, instanceBean); /** * 第三步: 属性赋值 * instanceA这类类里面有一个属性, InstanceB. 所以, 先拿到 instanceB, 然后在判断属性头上有没有Autowired注解. * 注意: 这里我们只是判断有没有Autowired注解. spring中还会判断有没有@Resource注解. @Resource注解还有两种方式, 一种是name, 一种是type */ Field[] declaredFields = beanClass.getDeclaredFields(); for (Field declaredField: declaredFields) { // 判断每一个属性是否有@Autowired注解 Autowired annotation = declaredField.getAnnotation(Autowired.class); if (annotation != null) { // 设置这个属性是可访问的 declaredField.setAccessible(true); // 那么这个时候还要构建这个属性的bean. /* * 获取属性的名字 * 真实情况, spring这里会判断, 是根据名字, 还是类型, 还是构造函数来获取类. * 我们这里模拟, 所以简单一些, 直接根据名字获取. */ String name = declaredField.getName(); /** * 这样, 在这里我们就拿到了 instanceB 的 bean */ Object fileObject = getBean(name); // 为属性设置类型 declaredField.set(instanceBean, fileObject); } } /** * 第四步: 初始化 * 初始化就是设置类的init-method.这个可以设置也可以不设置. 我们这里就不设置了 */ return instanceBean; }
还是上面的获取bean的流程, 不一样的是, 这里增加了以及缓存. 当我们获取到bean实例以后, 将其放入到缓存中. 下次再需要创建之前, 先去缓存里判断,是否已经有了, 如果没有, 那么再创建.
这样就给创建bean增加了一个出口. 不会循环创建了.
如上图所示, 在@Autowired的时候, 增加了一个出口. 判断即将要创建的类是否已经存在, 如果存在了, 那么就直接返回, 不在创建
虽然使用了一级缓存解决了循环依赖的问题, 但要是在多线程下, 这个依赖可能就会出现问题.
比如: 有两个线程, 同时创建instanceA 和instanceB, instanceA和instanceB都引用了instanceC. 他们同步进行, 都去创建instanceC. 首先A去创建, A在实例化instanceC以后就将其放入到一级缓存了, 这时候, B去一级缓存里拿. 此时拿到的instanceC是不完整的. 后面的属性赋值, 初始化都还没有执行呢. 所以, 我们增加二级缓存来解决这个问题.
5. 增加二级缓存, 区分完整的bean和纯净的bean.
public class MainStart { private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); // 一级缓存 private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 二级缓存 private static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(); /** * 读取bean定义, 当然在spring中肯定是根据配置 动态扫描注册的 * * InstanceA和InstanceB都有注解@Component, 所以, 在spring扫描读取配置类的时候, 会把他们两个扫描到BeanDefinitionMap中. * 这里, 我们省略这一步, 直接将instanceA和instanceB放到BeanDefinitionMap中. */ public static void loadBeanDefinitions(){ RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class); RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class); beanDefinitionMap.put("instanceA", aBeanDefinition); beanDefinitionMap.put("instanceB", bBeanDefinition); } public static void main(String[] args) throws Exception { // 第一步: 扫描配置类, 读取bean定义 loadBeanDefinitions(); // 第二步: 循环创建bean for (String key: beanDefinitionMap.keySet()) { // 第一次: key是instanceA, 所以先创建A类 getBean(key); } // 测试: 看是否能执行成功 InstanceA instanceA = (InstanceA) getBean("instanceA"); instanceA.say(); } /** * 获取bean, 根据beanName获取 */ public static Object getBean(String beanName) throws Exception { // 增加一个出口. 判断实体类是否已经被加载过了 Object singleton = getSingleton(beanName); if (singleton != null) { return singleton; } /** * 第一步: 实例化 * 我们这里是模拟, 采用反射的方式进行实例化. 调用的也是最简单的无参构造函数 */ RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName); Class<?> beanClass = beanDefinition.getBeanClass(); // 调用无参的构造函数进行实例化 Object instanceBean = beanClass.newInstance(); /** * 第二步: 放入到二级缓存 */ earlySingletonObjects.put(beanName, instanceBean); /** * 第三步: 属性赋值 * instanceA这类类里面有一个属性, InstanceB. 所以, 先拿到 instanceB, 然后在判断属性头上有没有Autowired注解. * 注意: 这里我们只是判断有没有Autowired注解. spring中还会判断有没有@Resource注解. @Resource注解还有两种方式, 一种是name, 一种是type */ Field[] declaredFields = beanClass.getDeclaredFields(); for (Field declaredField: declaredFields) { // 判断每一个属性是否有@Autowired注解 Autowired annotation = declaredField.getAnnotation(Autowired.class); if (annotation != null) { // 设置这个属性是可访问的 declaredField.setAccessible(true); // 那么这个时候还要构建这个属性的bean. /* * 获取属性的名字 * 真实情况, spring这里会判断, 是根据名字, 还是类型, 还是构造函数来获取类. * 我们这里模拟, 所以简单一些, 直接根据名字获取. */ String name = declaredField.getName(); /** * 这样, 在这里我们就拿到了 instanceB 的 bean */ Object fileObject = getBean(name); // 为属性设置类型 declaredField.set(instanceBean, fileObject); } } /** * 第四步: 初始化 * 初始化就是设置类的init-method.这个可以设置也可以不设置. 我们这里就不设置了 */ /** * 第二步: 放入到一级缓存 */ singletonObjects.put(beanName, instanceBean); return instanceBean; } /** * 判断是否是循环引用的出口. * @param beanName * @return */ 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; } } }
如上图所示,增加了一个二级缓存. 首先, 构建出instanceBean以后, 直接将其放入到二级缓存中. 这时只是一个纯净的bean, 里面还没有给属性赋值, 初始化. 在给属性赋值完成, 初始化完成以后, 在将其放入到一级缓存中.
我们判断缓存中是否有某个实例bean的时候, 先去一级缓存中判断是否有完整的bean, 如果没有, 就去二级缓存中判断有没有实例化过这个bean.
总结: 一级缓存和二级缓存的作用
一级缓存: 解决循环依赖的问题
二级缓存: 在创建实例bean和放入到一级缓存之间还有一段间隙. 如果在这之间从一级缓存拿实例, 肯定是返回null的. 为了避免这个问题, 增加了二级缓存.
我们都知道spring中有一级缓存, 二级缓存, 三级缓存. 一级缓存和二级缓存的作用我们知道了, 那么三级缓存有什么用呢?