Series blog posts:
[IoC-0] introduction of Spring IoC
[IoC-1] introduction of control inversion of IoC
[IoC-2] BeanDefinition scanning registration of IoC
[IoC-3] creation process of Spring bean
[IoC-4] dependency injection principle of IoC
[IoC-5] Spring IoC Summary - control inversion and dependency injection
Related reading:
@Difference between Resource and @ Autowired
Generation rules for bean name
BeanDefinition detailed analysis
preface
As we all know, Spring solves the problem of circular dependency. It can also be found on the Internet that Spring uses three-level cache to solve circular dependency.
Have you thought about why Spring uses L3 cache to solve the problem of circular dependency? Can I use two-level cache? Is it OK to use only L1 cache?
Which circular dependency scenarios can't be solved by Spring?
This article first analyzes how Spring solves the problem of circular dependency through three-level cache.
Later, we'll analyze why we use L3 cache and what cyclic dependency scenarios Spring can't solve.
text
We were analyzing earlier bean creation process It was mentioned that the creation of beans is triggered by AbstractBeanFactory#getBean().
Follow the source code of AbstractBeanFactory#getBean(java.lang.String), and you will find that it will call DefaultSingletonBeanRegistry#getSingleton()
// DefaultSingletonBeanRegistry#getSingleton() protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock Object singletonObject = this.singletonObjects.get(beanName); // There is no beanName in the L1 cache and beanName is being created if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); // There is no beanName in the bean's early reference, and early reference is allowed if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
The above is the code of L3 cache.
Let's take a good look at how it solves circular dependency!
L3 cache is defined as follows:
// DefaultSingletonBeanRegistry.java /** L1 cache: Cache of singleton objects: bean name to bean instance */ // Used to store bean s that have been fully initialized private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** L3 cache: Cache of singleton factories: bean name to ObjectFactory */ // It is used to store the early references of bean s (all will be stored, and it will be used only when circular dependency is applied) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** L2 Cache of early singleton objects: bean name to bean instance */ // It is used to store the objects obtained by the three-level cache ObjectFactory (only in case of circular dependency) private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
Prepare examples
We prepare the simplest example of circular dependency, a -- > scenario of a:
@Service public class AService { @Autowired AService aService; public AService getaService() { return aService; } } public class CircleTest { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.kvn.beans.circle"); AService a = applicationContext.getBean(AService.class); System.out.println(a + "--" + a.getaService()); } }
The program can run normally, and the AService injected in AService is itself.
This proves that Spring solves the problem of circular dependency.
Cycle dependent process analysis
AbstractBeanFactory#getBean() will first get the bean from the cache when triggering bean loading, that is, it will call the DefaultSingletonBeanRegistry#getSingleton() method.
Debugging DefaultSingletonBeanRegistry#getSingleton() through the simplest example above will find:
During the initialization of AService, when getSingleton() is called for the first time, there is no AService in singletonObjects in the first level cache, and the bean is not being created.
Later, when creating an instance of AService in createBean(), the early reference of the bean will be exposed in the three-level cache earlySingletonObjects through ObjectFactory.
When populateBean() injects dependency, getSingleton() will be called for the second time. At this time, AService is already being created, and there are early references of beans in the three-level cache earlySingletonObjects.
At this time, it can be injected.
In fact, when getSingleton() is called for the first time, there is no AService in all three caches
The early reference of the bean exposed in the third level cache earlySingletonObjects is an ObjectFactory object.
Breakpoint diagram of the second call to getSingleton():
When creating a bean, cache usage:
Analyze the usage of cache in combination with the bean creation process
The bean creation process is divided into three stages:
1 create an instance createBeanInstance
2 fill dependent populateBean
3 initializeBean
The following figure shows the bean creation process:
-
Without circular dependency:
After a new bean instance is created, the early reference of the bean will be exposed to the singletonFactories of the three-level cache through ObjectFactory.
After bean initialization, the corresponding L2 cache and L3 cache will be cleared, and the initialized beans will be put into the L1 cache singletonObjects.
-
In the case of cyclic dependency: (a -- > B -- > a scenario)
Summary
Spring solves the problem of circular dependency through three-level cache.
The definition and usage of L3 cache are as follows:
/** L1 cache: used to store fully initialized bean s */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** L2 cache: used to store objects obtained from L3 cache ObjectFactory */ // The L2 cache will not be stored for non cyclic dependency scenarios // In the scenario of circular dependency, objects will be obtained through the ObjectFactory of the L3 cache and put into the L2 cache for use private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); /** Level 3 cache: used to store early references to bean s */ // No matter whether there is circular dependency or not, the level 3 cache will be stored after createbean instance // In case of circular dependency, the third level cache will be used. The objects will be obtained through the third level cache, put into the second level cache, and delete the third level cache at the same time private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
If this article is helpful to you, welcome to like the collection!