循环依赖在编程中是一个常见问题(当然,这并不是最佳实践)。并且,Spring 如何解决循环依赖这个问题在面试中也经常见。下面,D瓜哥就从源码的层面深入剖析一下这个问题。
示例程序 先展示一下示例程序:
package com.diguage.truman.context; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; import org.springframework.stereotype.Component; /** * @author D瓜哥, https://www.diguage.com/ * @since 2020-05-24 13:02 */ public class CircularDependenceSingletonTest { public static final Log log = LogFactory.getLog(CircularDependenceSingletonTest.class); @Test public void test() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.register(Config.class); applicationContext.refresh(); log.info(applicationContext.getBean(A.class)); log.info(applicationContext.getBean(B.class)); log.info(applicationContext.getBean(C.class)); log.info("-A--------"); A a = applicationContext.getBean(A.class); log.info(a); log.info(a.b); log.info("-B--------"); B b = applicationContext.getBean(B.class); log.info(b); log.info(b.c); log.info("-C--------"); C c = applicationContext.getBean(C.class); log.info(c); log.info(c.a); } @Configuration @Import(AbcImportSelector.class) public static class Config { } public static class AbcImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{ A.class.getName(), B.class.getName(), C.class.getName()}; } } @Component public static class A { @Autowired B b; } @Component public static class B { @Autowired C c; } @Component public static class C { @Autowired A a; } } 上述示例代码中的循环依赖情况如下:
图 1. 循环依赖 源码剖析 三级缓存 D瓜哥在 深入剖析 Spring 核心数据结构:BeanFactory 中,概要性地对 BeanFactory 的属性做了一一说明。 而其中的“三级缓存”属性,则是解决循环依赖问题的关键所在:
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256):Bean 名称到单例 Bean 的映射,用于存放完全初始化好的 Bean。可以理解成,这就是所谓的容器。这是一级缓存。
Map<String, Object> earlySingletonObjects = new HashMap<>(16):Bean 到“未成熟”单例 Bean 的映射。该 Bean 对象只是被创建出来,但是还没有注入依赖。在容器解决循环依赖时,用于存储中间状态。这是二级缓存。
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16):Bean 名称到 Bean 的 ObjectFactory 对象的映射,存放 Bean 工厂对象。在容器解决循环依赖时,用于存储中间状态。这是三级缓存。
Bean 的获取过程就类似计算机缓存的作用过程:先从一级获取,失败再从二级、三级里面获取。在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean) 方法中,可以明确看到整个过程:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(beanName, allowEarlyReference) /** * Return the (raw) singleton object registered under the given name. * <p>Checks already instantiated singletons and also allows for an early * reference to a currently created singleton (resolving a circular reference). * @param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not * @return the registered singleton object, or {@code null} if none found */ @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
本文内容对于 Redis 7+ 来说已经过时,最新实现请看下面两篇文章:
Redis 核心数据结构(3)
Redis 核心数据结构(4)
Redis 目前是使用最广泛的缓存中间件。其突出特点就是支持多种常见的数据结构。对比 JDK 集合类的实现,Redis 的实现表现出很多独到之处,很多地方设计得别具匠心。下面就来简要介绍一下。
linkedlist Redis 底层也有很多地方使用到 linkedlist,并且也是双向链表。
adlist.h typedef struct listNode { struct listNode *prev; struct listNode *next; void *value; } listNode; typedef struct listIter { listNode *next; int direction; } listIter; typedef struct list { listNode *head; listNode *tail; void *(*dup)(void *ptr); void (*free)(void *ptr); int (*match)(void *ptr, void *key); unsigned long len; } list; Redis 的 linkedlist 实现特点是:
双向:节点带有前后指针;
无环:首尾没有相连,所以没有构成环状;
链表保存了首尾指针;
多态:可以保存不同类型的值,这里成为泛型也许更符合 Java 中的语义。
Redis 在 2014 年实现了 quicklist,并使用 quicklist 代替了 linkedlist。所以,现在 linkedlist 几乎已经是废弃状态。
ziplist Redis 官方在 ziplist.c 文件的注释中对 ziplist 进行了定义:
The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and integer values, where integers are encoded as actual integers instead of a series of characters. It allows push and pop operations on either side of the list in O(1) time. However, because every operation requires a reallocation of the memory used by the ziplist, the actual complexity is related to the amount of memory used by the ziplist.