Analysis of SpringIoC Source Execution Process

This article is a summary of my study of spring IoC when analyzing its source code. Its purpose is not to fully understand the source code information. But according to how spring IoC container initializes the instance from xml file to bean, a process is sorted out. I hope it can be helpful for you to understand the source code. Not much. Let's look at it directly.

Here, I use Application Context to introduce, after all, most of the projects still use this container. The entry that the container starts can be seen from the refresh method of the Abstract ApplicationContext class.
refresh

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

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

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

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

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

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

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

                // Initialize other special beans in specific context subclasses.
                onRefresh();

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

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

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

            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

There are two important ways to do this:

  • ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    This method is based on the Xml configuration file parsing bean information to generate BeanDifinition, and then registered to the BeanDefinition Registry, that is, the loading and registration stage of bean information.
  • beanFactory.preInstantiateSingletons();
    This method is understandable by its annotations, which instantiates all non-lazy loaded single bean s.

Entering the obtainFreshBeanFactory(), we can finally see the following method, which is to read and parse the information of the Bean from the XML file and put it in the corresponding BeanDefinition.

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }

Look at preInstantiate Singletons ()

public void preInstantiateSingletons() throws BeansException {
        if (this.logger.isInfoEnabled()) {
            this.logger.info("Pre-instantiating singletons in " + this);
        }
        List<String> beanNames;
        synchronized (this.beanDefinitionMap) {
            // 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.
            beanNames = new ArrayList<String>(this.beanDefinitionNames);
        }
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (isFactoryBean(beanName)) {
                    final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
                    boolean isEagerInit;
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                            public Boolean run() {
                                return ((SmartFactoryBean<?>) factory).isEagerInit();
                            }
                        }, getAccessControlContext());
                    }
                    else {
                        isEagerInit = (factory instanceof SmartFactoryBean &&
                                ((SmartFactoryBean<?>) factory).isEagerInit());
                    }
                    if (isEagerInit) {
                        getBean(beanName);
                    }
                }
                else {
                    getBean(beanName);
                }
            }
        }
    }

We need to focus on the getBean(beanName) method, which instantiates and initializes beans; we can go into this method and see the doGetBean() method.

protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {
            ... ...  
            //This part first retrieves the corresponding beans from the cache, and if so, returns the single beans in the cache.
            Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            if (logger.isDebugEnabled()) {
                if (isSingletonCurrentlyInCreation(beanName)) {
                    logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                            "' that is not fully initialized yet - a consequence of a circular reference");
                }
                else {
                    logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }
        
        else {
        
        ... ...
        /**
            * Return a merged RootBeanDefinition, traversing the parent bean definition
            * if the specified bean corresponds to a child bean definition.
            */
        final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        
            checkMergedBeanDefinition(mbd, beanName, args);

            // Guarantee initialization of beans that the current bean depends on.
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for (String dependsOnBean : dependsOn) {
                    getBean(dependsOnBean);
                    registerDependentBean(dependsOnBean, beanName);
                }
            }

            // Create bean instance.
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    public Object getObject() throws BeansException {
                        try {
                            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);
            }
        }
            }

In the code above, what we need to care about is

  • final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    Obtain the Bean Definition for the corresponding beanName, which is obviously done with some wrapping (RootBean Definition);
  • Because most beans in a project are singletons, we only care about the creation of singleton-type beans.
if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    public Object getObject() throws BeansException {
                        try {
                            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);
            }

Note that the getObject() method in ObjectFactory is reimplemented here, which is helpful for the following understanding. Let's go first into the getSingleton() method

public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            //Get the corresponding bean from the cache
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName,
                            "Singleton bean creation not allowed while the 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 recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<Exception>();
                }
                try {
                    singletonObject = singletonFactory.getObject();
                }
                catch (BeanCreationException ex) {
                    if (recordSuppressedExceptions) {
                        for (Exception suppressedException : this.suppressedExceptions) {
                            ex.addRelatedCause(suppressedException);
                        }
                    }
                    throw ex;
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    afterSingletonCreation(beanName);
                }
                //In this case, the instantiated bean s are put into the container.
                addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }

Here's what we need to pay attention to

  • beforeSingletonCreation(beanName);
    From the name, we can probably guess some of the functions of this method: what to do before a single bean is created;
  • singletonObject = singletonFactory.getObject();
    What do you think of when you see this? As mentioned above, the sub-method has been re-implemented in the previous layer, so we should pay attention to this section. It implements the method after re-implementation, and wait for specific analysis.
  • afterSingletonCreation(beanName);
    For beforeSingleton Creation, this is what you do after the creation of a single bean.

Next, let's look back at the redesigned singletonFactory.getObject() calling createBean(beanName, mbd, args);

We enter createBean(beanName, mbd, args)

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

        if (logger.isDebugEnabled()) {
            logger.debug("Creating instance of bean '" + beanName + "'");
        }
        // Make sure bean class is actually resolved at this point.
        resolveBeanClass(mbd, beanName);

        // Prepare method overrides.
        try {
            mbd.prepareMethodOverrides();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(mbd.getResourceDescription(),
                    beanName, "Validation of method overrides failed", ex);
        }

        try {
            // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
            Object bean = resolveBeforeInstantiation(beanName, mbd);
            if (bean != null) {
                return bean;
            }
        }
        catch (Throwable ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                    "BeanPostProcessor before instantiation of bean failed", ex);
        }

        Object beanInstance = doCreateBean(beanName, mbd, args);
        if (logger.isDebugEnabled()) {
            logger.debug("Finished creating instance of bean '" + beanName + "'");
        }
        return beanInstance;
    }

Here we can see

Object beanInstance = doCreateBean(beanName, mbd, args);

In this method, the instantiation of the record (some people will say at this time, how can we not see the real bean instantiation, no way, spring itself has to implement some of its own things, which we do not care about, but does not mean that it is not necessary, so patiently look at it)

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean.
        //This class will be placed in the container as the final bean singleton
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
        Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

        // Allow post-processors to modify the merged bean definition.
        synchronized (mbd.postProcessingLock) {
            if (!mbd.postProcessed) {
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                mbd.postProcessed = true;
            }
        }

        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
        //Dealing with cyclic dependencies
        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");
            }
            addSingletonFactory(beanName, new ObjectFactory() {
                public Object getObject() throws BeansException {
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }

// Initialize the bean instance.
        Object exposedObject = bean;
        try {
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                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);
            }
        }
        
... ... 

}

This method is long, we just need to care about what we need to care about.

  • BeanWrapper instanceWrapper = null;
    This class will be placed in the container as the final single bean, and this class will be an encapsulation of the corresponding bean.
  • applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
    Allowing other modifications to the bean Definition mainly allows other components to provide information that xml cannot provide. For example, Annotation is used to enhance the definition of beans. This is accomplished by the ergedBean DefinitionPostProcessor class, which invokes bean enhancement if such an implementation is provided in the container.
  • populateBean(beanName, mbd, instanceWrapper);
    In this method, the InstantiationAwareBeanPostProcessor is used to fill the bean s and to process the autowire tags.
  • exposedObject = initializeBean(beanName, exposedObject, mbd);
    Here comes the highlight, which is what we are most concerned about. Instances and initializations of bean s will be done inside.

Enter initializeBean

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    invokeAwareMethods(beanName, bean);
                    return null;
                }
            }, getAccessControlContext());
        }
        else {
            invokeAwareMethods(beanName, bean);
        }

        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        try {
            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()) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }

Here are three important ways

  • invokeAwareMethods(beanName, bean);
    Check whether the bean implements the relevant Aware interface, and if so, assemble it.
  • wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  • invokeInitMethods(beanName, wrappedBean, mbd);
    In this paper, Java reflection mechanism is used to set the bean Definition information corresponding to the bean.
  • wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

spring provides an interface class, BeanPostProcessor, which we call BeanPostProcessor. It should wrap beans in the instantiation process of beans. There are two methods, applyBeanPostProcessors BeforeInitialization and ApplyBeanPostProcessors AfterInitialization.

According to the above code, we know that before and after invokeInitMethods are executed, spring will call all BeanPostProcessor s to execute the methods, so we still need to look at the specific content of invokeInitMethods, and find that this method has two main functions:

  1. Determine whether the bean inherits InitializingBean, and if it inherits the interface, execute the afterPropertiesSet() method
  2. Gets whether the init-method property is set or not, and if so, executes the set method.

Here are some typical application scenarios such as:

  1. Resolve the bean's annotations and convert the fields in the annotations into attributes
  2. Unification injects attributes into beans before execution, such as sqlMap for database access, and severe services, so that attributes do not need to be configured for each bean
  3. Print logs, record time, etc.
    Detailed understanding of the use of BeanPostProcessor can be found here. Use of BeanPost Processor
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
            throws Throwable {

        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
            }
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                        public Object run() throws Exception {
                            ((InitializingBean) bean).afterPropertiesSet();
                            return null;
                        }
                    }, getAccessControlContext());
                }
                catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }
            else {
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }

        if (mbd != null) {
            String initMethodName = mbd.getInitMethodName();
            if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                    !mbd.isExternallyManagedInitMethod(initMethodName)) {
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }

Here spring really instantiates and initializes beans. So in the initializeBean method, we can probably summarize the initialization order of beans:
Implementation of XXAware Interface - > Post Process BeforeInitialization - > After Properties Set - > Custom Init Method of Initializing Bean - > Post Process AfterInitialization

If there are any mistakes, please correct them. If it's helpful to you, by the way, a Zambia!

Keywords: Java Spring xml Database

Added by ernielou on Sat, 08 Jun 2019 01:59:11 +0300