1, bean storage
Usually we say that the spring container helps us manage beans, but what is a bean?
For a simple example, if we provide an X.class class, the X.class at this time is called a class object. After a series of spring life cycle processing, it will become a bean object that can be placed in the spring container. We call the object at this time a bean object. Therefore, in summary, The class object processed by a series of life cycle methods is the bean object;
Only after being processed by a series of post processor methods is a qualified bean object.
Bean objects are finally stored in the spring container. Our simple and narrow spring container is a map collection at the bottom of the spring source code. The key stored in this map collection is the name of the current bean. If it is not specified, the default is that the initial lowercase of the class type is used as the key, and the value is the bean object. The map of the stored bean is in the DefaultListableBeanFactory class:
/** Map of bean definition objects, keyed by bean name. */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
When the Spring container scans the Bean class, it will save the description information of this class in the beanDefinitionMap in the form of package name and class name,
Map < String, BeanDefinition >, where String is the Key, the default is the initial lowercase of the class name, and BeanDefinition stores the class definition (description information). We usually call the BeanDefinition interface: the definition object of the bean.
Problem thinking
Why doesn't spring immediately execute the lifecycle method for initialization and instantiation after scanning the class file? Instead, put it into the beanDefinitionMap collection first?
Because in the container management function provided by spring, some class classes do not necessarily need to be initialized immediately. For example, the prototype bean is initialized when it is used.
Let's think about it. If it's a prototype bean and we don't have the beanDefinition layer, it's a big problem. For the prototype bean, I need to scan it every time I initialize it, which is a waste of time. At this time, the role of beanDefinition is obvious. In the process of starting, the spring container initializes and instantiates beans in two steps. The first step is to convert the class file into a beanDefinition object. The second step is to initialize and instantiate according to the beanDefinition object and the configuration requirements
With beanDefinition, beanDefinitionMap is equivalent to a transit station. All beans to be initialized are subject to the attribute information of beanDefinition in beanDefinitionMap.
1.spring has no additional requirements when converting the class file to beanDefinition. Whether the bean is singleton, prototype or lazy loaded, it will be scanned and put into the beanDefinitionMap collection.
2. When initializing the bean in the second step, you can judge whether the current bean is prototype, lazy loaded, dependent, etc
Therefore, we can also think that beanDefinition is a transit station in which all the information corresponding to the class is stored. If it is initialized, it will be initialized according to the attribute information of beanDefinition. Therefore, spring also provides a series of extension points for us to modify, add and delete beanDefinition.
So, simply put, we write a class file ourselves and put it into the spring container. It is a step:
A.class --> beanDefinition --> bean
Therefore, after adding a beanDefinition layer in the middle, it is likely to appear. We provide X.class, but after saving the class to beanDefinition, set the beanClass of the corresponding beanDefinition attribute to Y.class, and then the Y object is initialized instead of the X object.
This is just an example. What we want to explain is that we provide an X.class. Normally, the bean x is initialized. However, in special cases, we can modify the bean class of X's beanDefinition to Y.class, which will change the day. In fact, the Y object is initialized; This process depends on N extension points provided by Spring.
After putting the class into the bean definition map, you can modify the attribute information of the bean definition through the extension point.
Therefore, the X class we provide does not necessarily initialize the X bean. Many underlying frameworks complete their business logic through the extension point of beanDefinition.
What if we want to intervene during Spring class initialization?
Spring provides a series of extension points to handle the key points in the process of changing class files from class to bean objects according to our business needs. I think this is also the strength of spring. It not only helps us manage beans, but also provides corresponding extension points for us to extend spring according to our own needs. For example, beanfactoryprocessor allows us to intervene in beanDefinition objects in beanDefinitionMap; Spring's post processor allows us to intervene in the logic of each post processor method according to our own needs.
2, L3 cache of Bean
In the DefaultSingletonBeanRegistry class, a three-level cache is set for Bean creation:
First level cache: singletonObjects
Second level cache: earlySingletonObjects
Third level cache: singletonFactory
/** 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 ConcurrentHashMap<>(16);
It takes three steps to complete the creation of a Bean object
1. Create Bean instance (createBeanInstance)
-The main operation here is to instantiate an object from the previously processed beandefinition (Bean definition information).
2.Bean object attribute assignment (populateBean)
-The member variables modified by the @ Autowired annotation are injected in this step.
3. Initialization of Bean object (initialize Bean instance)
-This step will call the methods of the Bean that inherit some interfaces (BeanNameAware, etc.).
Create a Bean instance in the AbstractAutowireCapableBeanFactory class:
/** * Actually create the specified bean. Pre-creation processing has already happened * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks. * <p>Differentiates between default bean instantiation, use of a * factory method, and autowiring a constructor. * @param beanName the name of the bean * @param mbd the merged bean definition for the bean * @param args explicit arguments to use for constructor or factory method invocation * @return a new instance of the bean * @throws BeanCreationException if the bean could not be created * @see #instantiateBean * @see #instantiateUsingFactoryMethod * @see #autowireConstructor */ protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. 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; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); 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) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { 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 " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
Application timing of L3 cache
After the Bean object is instantiated, it will be put into the third level cache (addSingletonFactory)
if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
Application timing of L2 cache
After the Bean object is put into the third level cache, if future operations want to enter the cache query, it will
When the Bean objects in the L3 cache are moved to the L2 cache * *, the Bean objects placed in the L3 cache will be removed
/** * 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) { // Quick check for existing instance without full singleton lock Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); 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; }
Application timing of L1 cache
After the Bean object is created, it will be put into the L1 cache. (addSingleton)
Before a Bean object is used, it will try to get it from the cache. The first thing is to judge whether the first level cache exists, so it is called "first level".
/** * Return the (raw) singleton object registered under the given name, * creating and registering a new one if none registered yet. * @param beanName the name of the bean * @param singletonFactory the ObjectFactory to lazily create the singleton * with, if necessary * @return the registered singleton object */ 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 (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 + "'"); } beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { 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) { addSingleton(beanName, singletonObject); } } return singletonObject; } }
Level 3 cache is actually three maps, which are used at a specific time and on a specific occasion to solve specific problems.
(in fact, the whole Spring container is composed of many maps, which store environment information, Bean definition information, Bean instances, etc.)