Experimental environment: spring-framework-5.0.2,jdk8,gradle4.3.1
- Spring source code - IOC part - container introduction [1]
- Spring source code - IOC part - container initialization process [2]
- Spring source code - IOC part - Xml Bean parsing and registration process [3]
- Spring source code - IOC part - Custom IOC container and Bean parsing registration [4]
- Spring source code - IOC part - Bean instantiation process [5]
- Spring source code - IOC part - how spring solves Bean circular dependency [6]
This article focuses on how Spring solves circular dependency, and its core logic is in the getSingleton method. Under normal circumstances, this code is very common. It just checks whether the bean instance we want to get exists in the cache. If so, it returns the bean instance in the cache, otherwise it returns null. However, this code is the core code for Spring to solve circular reference.
Solve the circular reference logic: use the constructor to create an "incomplete" bean instance (incomplete because the bean instance is not initialized at this time), and expose the ObjectFactory of the bean instance in advance (early exposure is to put the ObjectFactory into the singletonFactories cache). Through ObjectFactory, we can get the reference of the bean instance. If circular reference occurs, we can get the bean instance through ObjectFactory in the cache, so as to avoid the dead cycle caused by circular reference. Although the bean instance obtained through the ObjectFactory in the cache is an "incomplete" bean instance, because it is a single instance, the reference address of the bean instance will not change after subsequent initialization, so what we finally see is a complete bean instance.
Let's first look at the execution flow of the getSingleton method
DefaultSingletonBeanRegistry#getSingleton method@Override @Nullable public Object getSingleton(String beanName) { return getSingleton(beanName, true); } /** * singletonObjects Cache: beanname - > singleton bean object. * earlySingletonObjects Cache: beanname - > singleton bean object. This cache stores the early singleton bean object. It can be understood that the attribute has not been filled and initialized. * singletonFactories Cache: beanname - > objectfactory. * singletonsCurrentlyInCreation Cache: the beanName collection of the singleton bean object is currently being created. * singletonObjects,earlySingletonObjects,singletonFactories Here, a concept similar to "L3 cache" is formed. */ @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 1. [get from L1 cache] get the singleton object corresponding to beanName from singleton object cache Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 3. Lock for operation synchronized (this.singletonObjects) { // 4. [get from L2 cache] get the singleton object from the early singleton object cache // It is called an early singleton object because the objects in earlySingletonObjects are created through the ObjectFactory exposed in advance, and no operations such as attribute filling have been carried out singletonObject = this.earlySingletonObjects.get(beanName); // 5. If it is not in the early singleton object cache, it is allowed to create early singleton object references if (singletonObject == null && allowEarlyReference) { // 6. [get from L3 cache] get the single instance factory of beanName from the single instance factory cache ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 7. If there is a singleton factory, create a singleton through the factory singletonObject = singletonFactory.getObject(); // 8. Put the singleton object created through the singleton object factory into the early singleton object cache this.earlySingletonObjects.put(beanName, singletonObject); // 9. Remove the singleton object factory corresponding to the beanName, because the singleton factory has created an instance object and put it into the earlySingletonObjects cache, // Therefore, the singleton object of beanName can be obtained through the earlySingletonObjects cache later, and the singleton factory does not need to be used this.singletonFactories.remove(beanName); } } } } return singletonObject; }
The code here is relatively simple, that is, the bean is obtained from the first, second and third level cache in turn, and null is not returned. So, can't help but have a few questions?
1. When is the bean instance put into the L3 cache?
When the bean is instantiated, the doGetBean method will be called. In the method, the getSingleton method will be called first to obtain it from the cache. If it cannot be obtained, the createBean method will be called to create the bean.
When bean is created, an early bean object is not instantiated by reflection or cglib, and then addSingletonFactory (beanName, () - > getEarlyBeanReference (beanName, MBD, bean)) is invoked. Put the early bean instance into the third level cache. Then is the property injection and bean initialization.
AbstractAutowireCapableBeanFactory#doCreateBean method/** * How to actually create bean s */ protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // Encapsulates the created bean object BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // TODO [important] this step creates a bean instance, which is just an early object and has not filled in the attribute value instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); // Gets the type of the instantiated object Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Call Post processors post processor synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Cache singleton mode bean s in the container to prevent circular reference boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isDebugEnabled()) { logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // TODO [important] add anonymous internal classes of bean instances to the level 3 cache to prevent circular references and hold references to objects as soon as possible addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. After initialization, exposedObject returns the bean after dependency injection Object exposedObject = bean; try { // TODO [important] bean attribute dependency injection, and assign the attribute value configured in the bean definition to the instance object populateBean(beanName, mbd, instanceWrapper); // TODO [important] start initializing bean object, call Aware interface method - > call beanpostprocessor Before method - > call init method method - > call beanpostprocessor After method exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { // Gets the registered singleton bean with the specified name Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // The registered bean obtained by name is the same as the bean being instantiated if (exposedObject == bean) { // The currently instantiated bean is initially completed exposedObject = earlySingletonReference; } // The current bean depends on other beans, and it is not allowed to create new instance objects when sending circular references else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { // Get other beans that the current bean depends on String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { // Type check on dependent bean s if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register the bean that completes dependency injection try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
2. When is the bean instance put into the L2 cache?
In fact, in the DefaultSingletonBeanRegistry#getSingleton method, get the bean from the L3 cache, put it into the L2 cache, and then remove it from the L3 cache.
3. When is the bean instance put into the L1 cache?
In the doGetBean method, the getSingleton method will be called first to get from the cache. If it cannot be obtained, the createBean method will be called to create the bean. When creating the bean, it will go through the steps of instantiation, attribute injection and initialization. At this time, the bean is already a complete bean object. Return it, call the addSingleton method to store it in the first level cache, and then from the second level L3 cache removal.
DefaultSingletonBeanRegistry#getSingleton method/** * Create bean instances through singletonFactory and store them in the first level cache singletonObjects */ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // If the current factory instance is being destroyed, it is not allowed to create and an exception is thrown if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); } if (logger.isDebugEnabled()) { logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); } // Verify whether it is being created and throw an exception beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { // [create instance here] singletonFactory creates bean instance singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> // if yes, proceed with it since the exception indicates that state. singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { // TODO [important] add the bean instance to the cache addSingleton(beanName, singletonObject); } } return singletonObject; } }
4. What's the problem with caching only one level?
If only the first level cache is used, the instantiated early beans will be put into the first level cache (there is no attribute injection at this time). At this time, the beans are not available, but they can be taken directly from the first level cache through the getBean method, so it must not work.
5. What's the problem with using only two levels of cache?
If the early beans are put into the third level cache, after the bean dependency injection and initialization are completed, they will not be put into the second level cache, but directly into the first level cache. If it is an ordinary bean, this method is feasible.
However, it is not feasible for proxy beans to implement AOP. If we still implement it in this way, the three-level cache singlefactory When GetObject () gets the object, it will generate a new proxy object. In the process of bean attribute injection, each call will generate a new proxy object. What we want is a singleton bean, which is definitely not feasible. Therefore, through the L2 cache, a proxy object is generated from the L3 cache and put into the L2 cache, and then it is taken from the L2 cache to ensure the single instance of the bean. Finally, after the creation is completed, it will be put into the L1 cache.
reference material:
Tan Yongde, author of "spring 5 core principles and 30 class handwriting"
Author Hao Jia of "deep analysis of Spring source code"
Ji Wenke, author of Spring technology insider