The finishBeanFactoryInitialization method in the eighth part of Spring source code analysis is to initialize the singleton bean

Posted by khendar on Wed, 09 Mar 2022 04:25:29 +0100

The finishBeanFactoryInitialization method in the eighth part of Spring source code analysis is to initialize the singleton bean

A profound interpretation of the seven thousand word long article, how to initialize the singleton bean in Spirng, and how does Spring solve the circular dependency most often asked in the interview?

Today, the most important method of Spring core method refresh() is the finishBeanFactoryInitialization() method, which is responsible for initializing all singleton beans.

The finishBeanFactoryInitialization() method is located at the subscript 8 in refresh().

So far, it should be said that the BeanFactory has been created, and all beans that implement the BeanFactory postprocessor interface have been initialized, and the postProcessBeanFactory(factory) method has been callback executed. Moreover, Spring has "manually" registered some special beans, such as environment, systemProperties, etc.

The rest is to initialize singleton beans. Most of our businesses are singleton beans. For example, the classes of @ Controller and @ Service (without lazy loading) are initialized in this place for our use. If lazy loading is not set, Spring will initialize all singleton beans next.

Let's first take a look at the source code of refresh(), and roughly look at the location of finishBeanFactoryInitialization(beanFactory).

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            //1. Preparation before refresh
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            //2. Bean factory will be initialized, beans will be loaded, and beans will be registered
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            //3. Set the class loader of BeanFactory, add several beanpostprocessors, and manually register several special beans
            prepareBeanFactory(beanFactory);

            try {
                //4. Template method
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                //Execute BeanFactory post processor
                invokeBeanFactoryPostProcessors(beanFactory);

                // 5,Register bean processors that intercept bean creation.
                //Register bean post processor
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                //internationalization
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                //6. Template method -- springboot implements this method
                onRefresh();

                // Check for listener beans and register them.
                //7. Register listener
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                //8. It is important to complete the initialization * * method of the bean factory**********************************************
                finishBeanFactoryInitialization(beanFactory);

                //9, Last step: publish corresponding event.
                finishRefresh();
            }

We go deep into the finishBeanFactoryInitialization(beanFactory), where the calling lines are complex. We hope readers can be prepared.

/**
     * Responsible for initialization of singleton bean
     * Finish the initialization of this context's bean factory,
     * initializing all remaining singleton beans.
     */
    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        // Initialize conversion service for this context.
        //First initialize the class named conversionService, which is used to bind the parameters passed from the front end to the parameters on the back-end controller method
        //Especially for conversion of non basic types
        if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
                beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
            beanFactory.setConversionService(
                    //Initialization is implemented in the getBean() method
                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
        }

        // Register a default embedded value resolver if no bean post-processor
        // (such as a PropertyPlaceholderConfigurer bean) registered any before:
        // at this point, primarily for resolution in annotation attribute values.
        if (!beanFactory.hasEmbeddedValueResolver()) {
            beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
        }

        // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
        // First initialize the Bean aop of loadtimeweaveaware type and pay attention to it: there is probably an impression that the aop will be concatenated with it in the future.
        String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
        for (String weaverAwareName : weaverAwareNames) {
            getBean(weaverAwareName);
        }

        // Stop using the temporary ClassLoader for type matching.
        beanFactory.setTempClassLoader(null);

        // Allow for caching all bean definition metadata, not expecting further changes.
        //The word freeze means freeze. At this time, pre initialization has started, and bean definition parsing, loading and registration have stopped first
        beanFactory.freezeConfiguration();

        // Instantiate all remaining (non-lazy-init) singletons.
        //Start initialization
        beanFactory.preInstantiateSingletons();
    }

This method is to judge whether a series of beans belong to a certain type of beans. If so, call getBean () method. If not, call beanfactory Preinstantiatesingletons() to initialize, let's put getBean() first and focus on beanfactory Preinstantiatesingletons() method.

@Override
    public void preInstantiateSingletons() throws BeansException {
        if (logger.isTraceEnabled()) {
            logger.trace("Pre-instantiating singletons in " + this);
        }

        // Iterate over a copy to allow for init methods which in turn register new bean definitions.
        // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
        // this.beanDefinitionNames saves all beanNames

        List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

        // Trigger initialization of all non-lazy singleton beans...
        ////The following loop triggers the initialization of all non lazy loaded singleton beans
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            // Non abstract, non lazy loaded singletons. If 'abstract = true' is configured, initialization is not required
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                // Process FactoryBean (bean responsible for initializing the factory)
                if (isFactoryBean(beanName)) {
                    // For FactoryBean, add '&' before beanName
                    //The getBean() method is called here
                    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                    if (bean instanceof FactoryBean) {
                        FactoryBean<?> factory = (FactoryBean<?>) bean;
                        // Judge whether the current FactoryBean is the implementation of SmartFactoryBean
                        boolean isEagerInit;
                        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                            isEagerInit = AccessController.doPrivileged(
                                    (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                                    getAccessControlContext());
                        }
                        else {
                            isEagerInit = (factory instanceof SmartFactoryBean &&
                                    ((SmartFactoryBean<?>) factory).isEagerInit());
                        }
                        if (isEagerInit) {
                            getBean(beanName);
                        }
                    }
                }
                else {
                    // For ordinary beans, you can initialize them by calling the get Bean (beanname) method
                    getBean(beanName);
                }
            }
        }

        // Trigger post-initialization callback for all applicable beans...
        // This shows that all non lazy loaded singleton beans have been initialized
        // If the bean we defined implements the smartinitializingsingsingleton interface, we will get a callback here
        //If you want to do something after the singleton bean is initialized, implement the interface
        for (String beanName : beanNames) {
            Object singletonInstance = getSingleton(beanName);
            if (singletonInstance instanceof SmartInitializingSingleton) {
                SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
                if (System.getSecurityManager() != null) {
                    AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                        smartSingleton.afterSingletonsInstantiated();
                        return null;
                    }, getAccessControlContext());
                }
                else {
                    smartSingleton.afterSingletonsInstantiated();
                }
            }
        }
    }

The main task of the preInstantiateSingletons() method is to initialize, and it is also a series of judgements before initialization, such as whether it is lazy loading, whether it is a factorybean (a special bean, which is responsible for the bean created by the factory), and finally calls the getBean() method.

Whether one episode implements the smartinitializingsingsingleton interface allows you to do something after bean initialization. Let's write a simple example to test it.

Readers in other places can take a look at the comments, and we begin to go deeper into the getBean() method.

doGetBean() is called inside the getBean() method. Let's look at the doGetBean method directly.

    
    // We are analyzing the process of initializing beans, but the get Bean method is often used to obtain beans from containers. Pay attention to switching ideas,
    // After initialization, it will be returned directly from the container. Otherwise, it will be initialized first and then returned
    protected <T> T doGetBean(
            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {

        // Get an "orthodox" beanName and handle two situations. One is the factorybean (with '&' in front),
        // One is the alias problem, because this method is used to get beans. If you pass in an alias, it is entirely possible
        String beanName = transformedBeanName(name);
        // Return value
        Object bean;

        // Eagerly check singleton cache for manually registered singletons.
        // Check if it has been created
        Object sharedInstance = getSingleton(beanName);
        // Here's args, although it doesn't seem to matter at all. When we came in all the way ahead, we were all getBean(beanName),
        // Therefore, the args parameter is actually null, but if args is not empty, it means that the caller does not want to obtain the Bean, but creates the Bean
        if (sharedInstance != null && args == null) {
            if (logger.isTraceEnabled()) {
                if (isSingletonCurrentlyInCreation(beanName)) {
                    logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                            "' that is not fully initialized yet - a consequence of a circular reference");
                }
                else {
                    logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }
            // The following method: if it is an ordinary Bean, return sharedInstance directly,
            // If it is a FactoryBean, return the instance object it created
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }

        else {
            // Fail if we're already creating this bean instance:
            // We're assumably within a circular reference.
            // If you have created a bean of the prototype type of this beanName, throw an exception,
            // It is often because of falling into circular reference. It turns out that the previous circular dependencies were thrown exceptions here. If there is another problem, it will not be a headless fly
            if (isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }

            // Check if bean definition exists in this factory.
            // Check whether the BeanDefinition exists in the container. The BeanDefinition contains a series of information about the bean
            BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                // Not found -> check parent.
                // If the BeanDefinition does not exist in the current container, try whether it exists in the parent container
                String nameToLookup = originalBeanName(name);
                if (parentBeanFactory instanceof AbstractBeanFactory) {
                    return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                            nameToLookup, requiredType, args, typeCheckOnly);
                }
                else if (args != null) {
                    // Delegation to parent with explicit args.
                    // Returns the query result of the parent container
                    return (T) parentBeanFactory.getBean(nameToLookup, args);
                }
                else if (requiredType != null) {
                    // No args -> delegate to standard getBean method.
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
                else {
                    return (T) parentBeanFactory.getBean(nameToLookup);
                }
            }

            if (!typeCheckOnly) {
                // typeCheckOnly is false. Put the current beanName into an alreadyCreated Set set.
                markBeanAsCreated(beanName);
            }

            /*
             * To sum up:
             * If you are here, you should be ready to create a Bean. For singleton beans, this Bean has not been created in the container;
             * For a prototype Bean, it is necessary to create a new Bean.
             */
            try {
                RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                // Initialize all dependent beans first, which is well understood.
                // Note that dependencies here refer to dependencies defined in dependencies on
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    for (String dep : dependsOn) {
                        // Check whether there is circular dependency. The circular dependency here is different from the circular dependency we mentioned earlier
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        // Register dependencies
                        registerDependentBean(dep, beanName);
                        try {
                            // Initialize dependent items first
                            getBean(dep);
                        }
                        catch (NoSuchBeanDefinitionException ex) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                        }
                    }
                }

                // Create bean instance.
                // If it is a singleton scope, create an instance of singleton
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            // Execute the creation of Bean, and the details will continue to deepen
                            // The third parameter args array represents the parameters needed to create the instance, which is not the parameters used for the construction method or the parameters of the factory Bean. However, it should be noted that args is null in our initialization stage.
                            // This time we are going to a new class AbstractAutowireCapableBeanFactory. Look at the class name, AutowireCapable? Does the class name also explain some problems.
                            // Mainly for the following scenarios, @ Autowired annotation is used to inject attribute values:
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // Explicitly remove instance from singleton cache: It might have been put there
                            // eagerly by the creation process, to allow for circular reference resolution.
                            // Also remove any beans that received a temporary reference to the bean.
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
                // If it is a prototype scope, create an instance of prototype
                else if (mbd.isPrototype()) {
                    // It's a prototype -> create a new instance.
                    Object prototypeInstance = null;
                    try {
                        beforePrototypeCreation(beanName);
                        // Execute create Bean
                        prototypeInstance = createBean(beanName, mbd, args);
                    }
                    finally {
                        afterPrototypeCreation(beanName);
                    }
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }

                else {
                    String scopeName = mbd.getScope();
                    if (!StringUtils.hasLength(scopeName)) {
                        throw new IllegalStateException("No scope name defined for bean ยด" + beanName + "'");
                    }
                    Scope scope = this.scopes.get(scopeName);
                    if (scope == null) {
                        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                    }
                    try {
                        Object scopedInstance = scope.get(beanName, () -> {
                            beforePrototypeCreation(beanName);
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            finally {
                                afterPrototypeCreation(beanName);
                            }
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }
                    catch (IllegalStateException ex) {
                        throw new BeanCreationException(beanName,
                                "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                ex);
                    }
                }
            }
            catch (BeansException ex) {
                cleanupAfterBeanCreationFailure(beanName);
                throw ex;
            }
        }

        // Check if required type matches the type of the actual bean instance.
        // Finally, check whether the type is correct. If it is wrong, throw an exception, and if it is right, return
        if (requiredType != null && !requiredType.isInstance(bean)) {
            try {
                T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
                if (convertedBean == null) {
                    throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
                }
                return convertedBean;
            }
            catch (TypeMismatchException ex) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Failed to convert bean '" + name + "' to required type '" +
                            ClassUtils.getQualifiedName(requiredType) + "'", ex);
                }
                throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
            }
        }
        return (T) bean;
    }

The specific instantiation process is in the createBean() method. We continue to go deep into the createBean() method.

@Override
    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {

        if (logger.isTraceEnabled()) {
            logger.trace("Creating instance of bean '" + beanName + "'");
        }
        RootBeanDefinition mbdToUse = mbd;

        // Make sure bean class is actually resolved at this point, and
        // clone the bean definition in case of a dynamically resolved Class
        // which cannot be stored in the shared merged bean definition.
        // Ensure that the Class in BeanDefinition is loaded
        Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
        if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
            mbdToUse = new RootBeanDefinition(mbd);
            mbdToUse.setBeanClass(resolvedClass);
        }

        // Prepare method overrides.
        // Preparing method overrides involves another concept: MethodOverrides, which comes from < lookup method / > in the bean definition
        // And < replaced method / >. If the reader is interested, go back to the place of bean parsing to see the parsing of these two tags.
        try {
            mbdToUse.prepareMethodOverrides();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
                    beanName, "Validation of method overrides failed", ex);
        }

        try {
            // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
            // Let the instantiaawarebeanpostprocessor have the opportunity to return to the agent at this step,
            // It is explained in the article "Spring AOP source code analysis", which is skipped here
            Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
            if (bean != null) {
                return bean;
            }
        }
        catch (Throwable ex) {
            throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                    "BeanPostProcessor before instantiation of bean failed", ex);
        }

        try {
            // Play, creating bean s
            Object beanInstance = doCreateBean(beanName, mbdToUse, args);
            if (logger.isTraceEnabled()) {
                logger.trace("Finished creating instance of bean '" + beanName + "'");
            }
            return beanInstance;
        }
        catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
            // A previously detected exception with proper bean creation context already,
            // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
        }
    }

Let's continue to look at the doCreateBean method. The calling process is really deep.

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) {
            // The description is not FactoryBean. Here is the instantiation of Bean, which is very important. We'll talk about the details later**********
            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.
        // The following code is to solve the problem of circular dependency. This is an important play to solve the problem of circular dependency
        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 {
            // This step is also very critical. This step is responsible for attribute assembly, because the previous instance is only instantiated and has no set value. Here is the set value***************
            populateBean(beanName, mbd, instanceWrapper);
            // Remember init method? And the InitializingBean interface? And the BeanPostProcessor interface?
            // Here are various callbacks after bean initialization**************
            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);
            }
        }
    // The following code is to solve the problem of circular dependency. This is an important play to solve the problem of circular dependency    
        if (earlySingletonExposure) {
                //Core method call of circular dependency
            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;
    }

So far, we have analyzed the doCreateBean method. In general, we have finished the whole initialization process.

There is a particularly important knowledge point after instantiating bean s, which is also the most frequently asked question in the interview. How does Spring solve the problem of circular dependency? The core code is in this method.

Circular dependency is actually circular reference, that is, two or more bean s hold each other, and finally form A closed loop. For example, A depends on B, B depends on C, and C depends on A. As shown below:

The doCreateBean method has three core processes.

(1) createBeanInstance: instantiation, that is to call the constructor of the object to instantiate the object

(2) populateBean: fill in attributes. This step is mainly to fill in the dependent attributes of multiple beans

(3) initializeBean: call the init method in spring xml.

From the single instance bean initialization steps described above, we can know that circular dependency mainly occurs in the first and second steps. That is, constructor loop dependency and field loop dependency.

In order to solve the circular reference problem, we should also start with the initialization process. For a single instance, there is only one object in the whole life cycle of the spring container, so it is easy to think that this object should exist in the Cache. Spring uses a three-level Cache to solve the circular dependency problem of a single instance.

Let's look at the getSingleton method.

This method also relies on three map s, which are the three-level cache.

/** Cache of singleton objects: bean name to bean instance. */
//cache of singleton object
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
// cache of singleton object factory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
//Cache of pre exposed singleton object
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
@Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        Object singletonObject = this.singletonObjects.get(beanName);
        
        //Judge whether the current singleton bean is being created, that is, it has not been initialized (for example, the constructor of A depends on the B object, so you have to create the B object first
        // Or if A relies on the B object in the populateBean process of A, the B object must be created first. At this time, A is in the state of being created.
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            
            // Is it allowed to get objects from singletonFactories through getObject
            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;
    }

To analyze the whole process of getSingleton(), Spring first obtains the singletonObjects from the first level cache. If you can't get it and the object is being created, you can get it from the L2 cache earlySingletonObjects.

If singletonFactories cannot be obtained and are allowed to be obtained through GetObject (), singletonFactories will be obtained from the three-level cache getObject() (three-level cache). If it is obtained, then:

this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                        

Place in singletons and remove from singletons. In fact, it is moving from the third level cache to the second level cache.

From the analysis of the three-level cache above, we can know that the trick of Spring to solve circular dependency lies in the three-level cache of singleton factories.

This code takes place after createBeanInstance, that is, the singleton object has been created (the constructor has been called). This object has been produced. Although it is not perfect (the second and third steps of initialization have not yet been carried out), it can be recognized (the object can be located in the heap according to the object reference). Therefore, Spring will expose this object in advance for everyone to know and use.

What are the benefits of doing so?

Let's analyze the circular dependency of "A field or setter of A depends on the instance object of B, while A field or setter of B depends on the instance object of A".

In the first step of the process, A finds the dependent object, and B initializes it in advance (at this time, A finds the dependent object in the first step, and B starts to initialize it in the second step). At this time, A finds the dependent object in the first step, and B starts to initialize it in advance, Try the first level cache singletonObjects (definitely not, because A has not been fully initialized), try the second level cache earlySingletonObjects (also not), and try the third level cache singletonFactories. Because A exposes itself in advance through ObjectFactory, B can pass through ObjectFactory GetObject gets object A (although A has not been initialized completely, it is better than nothing). After getting object A, B successfully completes initialization stages 1, 2 and 3. After complete initialization, it puts itself into the first level cache singletonObjects.

At this time, return to A. at this time, a can get the object of B and successfully complete its initialization stages 2 and 3. Finally, a also completes the initialization and goes into the first level cache singletonObjects. What's more, because B gets the object reference of a, the object of a that B now hold s has completed the initialization.

When you know this principle, you must know why Spring can't solve the problem of "A's construction method depends on B's instance object, and B's construction method depends on a's instance object"! Because the premise of adding singletonFactories L3 cache is that the constructor is executed, the cyclic dependency of the constructor cannot be solved.

Next, let's pick out three details in doCreateBean. One is the createBeanInstance method of creating Bean instances, the other is the populateBean method of dependency injection, and the other is the callback method initializeBean.

These three methods are also extremely complex. Readers who are interested can continue to go deep into them.

1. createBeanInstance method

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
        // Make sure bean class is actually resolved at this point.
        // Make sure this class is loaded
        Class<?> beanClass = resolveBeanClass(mbd, beanName);

        // Check the access rights of this class
        if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                    "Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
        }

        Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
        if (instanceSupplier != null) {
            return obtainFromSupplier(instanceSupplier, beanName);
        }

        if (mbd.getFactoryMethodName() != null) {
            // The factory method is adopted for instantiation. Readers who are not familiar with this concept can see the appendix. Note that it is not FactoryBean
            return instantiateUsingFactoryMethod(beanName, mbd, args);
        }

        // Shortcut when re-creating the same bean...
        // If it is not created for the first time, for example, the prototype bean is created for the second time.
        // In this case, we can know from the first creation whether to use parameterless constructor or constructor dependency injection to complete instantiation
        boolean resolved = false;
        boolean autowireNecessary = false;
        if (args == null) {
            synchronized (mbd.constructorArgumentLock) {
                if (mbd.resolvedConstructorOrFactoryMethod != null) {
                    resolved = true;
                    autowireNecessary = mbd.constructorArgumentsResolved;
                }
            }
        }
        if (resolved) {
            if (autowireNecessary) {
                // Constructor Dependency Injection 
                return autowireConstructor(beanName, mbd, null, null);
            }
            else {
                // non-parameter constructor 
                return instantiateBean(beanName, mbd);
            }
        }

        // Candidate constructors for autowiring?
        // Determine whether to use a parametric constructor
        Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
                mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
            // Constructor Dependency Injection 
            return autowireConstructor(beanName, mbd, ctors, args);
        }

        // Preferred constructors for default construction?
        ctors = mbd.getPreferredConstructors();
        if (ctors != null) {
            // Constructor Dependency Injection 
            return autowireConstructor(beanName, mbd, ctors, null);
        }

        // No special handling: simply use no-arg constructor.
        // Call parameterless constructor
        return instantiateBean(beanName, mbd);
    }

Take a look at how the instantiateBean method does it.

protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) {
        try {
            Object beanInstance;
            if (System.getSecurityManager() != null) {
                beanInstance = AccessController.doPrivileged(
                        (PrivilegedAction<Object>) () -> getInstantiationStrategy().instantiate(mbd, beanName, this),
                        getAccessControlContext());
            }
            else {
                // instantiation 
                beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
            }
            // Wrap it up and return
            BeanWrapper bw = new BeanWrapperImpl(beanInstance);
            initBeanWrapper(bw);
            return bw;
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
        }
    }

We can see that the key is: beaninstance = getinstantiationstrategy() instantiate(mbd, beanName, parent);

Inside is the specific instantiation process. Let's go in and have a look.

@Override
    public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
        // Don't override the class with CGLIB if no overrides.
        // If there is no method override, use java reflection for instantiation, otherwise use CGLIB,
        // For method override, see the introduction of lookup method and replaced method in the appendix "method injection"
        if (!bd.hasMethodOverrides()) {
            Constructor<?> constructorToUse;
            synchronized (bd.constructorArgumentLock) {
                constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
                if (constructorToUse == null) {
                    final Class<?> clazz = bd.getBeanClass();
                    if (clazz.isInterface()) {
                        throw new BeanInstantiationException(clazz, "Specified class is an interface");
                    }
                    try {
                        if (System.getSecurityManager() != null) {
                            constructorToUse = AccessController.doPrivileged(
                                    (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
                        }
                        else {
                            constructorToUse = clazz.getDeclaredConstructor();
                        }
                        bd.resolvedConstructorOrFactoryMethod = constructorToUse;
                    }
                    catch (Throwable ex) {
                        throw new BeanInstantiationException(clazz, "No default constructor found", ex);
                    }
                }
            }
            // Instantiation using construction method
            return BeanUtils.instantiateClass(constructorToUse);
        }
        else {
            // Must generate CGLIB subclass.
            // There is method override, and CGLIB is used to complete instantiation. It needs to rely on CGLIB to generate subclasses, which will not be expanded here.
            // tips: because if CGLIB is not used, there is an override. JDK does not provide corresponding instantiation support
            return instantiateWithMethodInjection(bd, beanName, owner);
        }
    }

At this point, we're done instantiating. Let's start with how to inject attributes.

2. populateBean method

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
        if (bw == null) {
            if (mbd.hasPropertyValues()) {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
            }
            else {
                // Skip property population phase for null instance.
                return;
            }
        }

        // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
        // state of the bean before properties are set. This can be used, for example,
        // to support styles of field injection.
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof InstantiationAwareBeanPostProcessor) {
                    InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                        return;
                    }
                }
            }
        }

        // All the properties of the bean instance are here
        PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

        int resolvedAutowireMode = mbd.getResolvedAutowireMode();
        // Find all attribute values by name. If it is a bean dependency, initialize the dependent bean first. Record dependencies
        if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
            MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
            // Add property values based on autowire by name if applicable.
            if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
                autowireByName(beanName, mbd, bw, newPvs);
            }
            // Add property values based on autowire by type if applicable.
            if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
                autowireByType(beanName, mbd, bw, newPvs);
            }
            pvs = newPvs;
        }

        boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
        boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

        PropertyDescriptor[] filteredPds = null;
        if (hasInstAwareBpps) {
            if (pvs == null) {
                pvs = mbd.getPropertyValues();
            }
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof InstantiationAwareBeanPostProcessor) {
                    InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
                    if (pvsToUse == null) {
                        if (filteredPds == null) {
                            filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                        }
                        // There is a very useful BeanPostProcessor here: Autowired annotation BeanPostProcessor
                        // Set the Value of dependencies annotated with @ Autowired and @ Value. The content here is also very rich
                        pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                        if (pvsToUse == null) {
                            return;
                        }
                    }
                    pvs = pvsToUse;
                }
            }
        }
        if (needsDepCheck) {
            if (filteredPds == null) {
                filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
            }
            checkDependencies(beanName, mbd, filteredPds, pvs);
        }

        if (pvs != null) {
            // Set the property value of the bean instance
            applyPropertyValues(beanName, mbd, bw, pvs);
        }
    }

After the property injection is completed, this step is actually dealing with various callbacks. This code is relatively simple.

3. initializeBean method

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                invokeAwareMethods(beanName, bean);
                return null;
            }, getAccessControlContext());
        }
        else {
            // If the bean implements the BeanNameAware, BeanClassLoaderAware or beanfactory aware interfaces, the callback
            invokeAwareMethods(beanName, bean);
        }

        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
            // postProcessBeforeInitialization callback of BeanPostProcessor
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        try {
            // Handle the init method defined in the bean,
            // Or if the bean implements the InitializingBean interface, call the afterpropertieset () method
            invokeInitMethods(beanName, wrappedBean, mbd);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
        }
        if (mbd == null || !mbd.isSynthetic()) {
            // postProcessAfterInitialization callback for BeanPostProcessor
            //Both callbacks of BeanPostProcessor occur here, but init method is processed in the middle
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }

        return wrappedBean;
    }

Since then, Spring has completed the process of instantiating a singleton non lazy loaded bean, which is also the most important method of Spirng. In our daily use of Spring, we define various classes, and then add @ Controller,@Service,Autowired and other annotations on them. How do these annotations work?

Presumably, most of the students know it and don't know why. Presumably, through this article, readers can have a clear understanding in their hearts.

Topics: Java Spring Spring Boot Back-end