Spring container initialization

Posted Jun 6, 202012 min read

After reading this article you will gain

  • Understand the Spring container initialization process
  • Best practice of ThreadLocal in Spring
  • Answer the Spring container initialization process during the interview

introduction

Let's start with a simple and common code

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <bean class="com.demo.data.Person">
        <description>
            WeChat search:CoderLi(may you pay attention  ? This time must be?)
        </description>
    </bean>
</beans>

public static void main(String[]args) {
        Resource resource = new ClassPathResource("coderLi.xml");
        DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
        xmlBeanDefinitionReader.loadBeanDefinitions(resource);
    }

The above Java code mainly does

  • Access to resources(location)
  • Create a beanFactory
  • Create a beanDefinitionReader based on beanFactory(implementing BeanDefinitionRegistry interface)
  • Load the resource and register the beanDefinition in the resource

So in general it is the three steps of resource loading, loading and registration

Component introduction

Before analyzing the source code process, let's get familiar with some important components

DefaultListableBeanFactory

defaultListableBeanFactory is the core part of the whole bean loading, and is the default implementation of bean registration and bean loading

defaultListableBeanFactory class diagram

For AliasRegistry can refer to another post my article Spring-AliasRegistry . We only need to remember two points about this class, one is that it is a beanFactory and the other is that it is a BeanDefinitionRegistry

XmlBeanDefinitionReader

Functions read from XML resource files and converted to BeanDefinition

XmlBeanDefinitionReader class diagram

DocumentLoader

Convert Resource files and convert Resource files to Document files

DocumentLoader class diagram

BeanDefinitionDocumentReader

Read Document and register with BeanDefinitionRegistry

BeanDefinitionDocumentReader

Source code analysis

loadBeanDefinitions(Resource)

XmlBeanDefinitionReader#loadBeanDefinitions(Resource) Let s start with this entry method

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(new EncodedResource(resource));
}

EncodedResource is a subclass of Resource, [Spring- resource loading(source code analysis)]( https://mp.weixin.qq.com/s?__biz=MzUwOTk0MTA3MQ==&mid=2247483828&idx=1&sn=6d8902e3ae729b8f9d7b5af992bf595b&chksm=f90bc594ce7c4c820636a73cbe1ddedeb5f220e0522d6b268f3420dd36601764643eef8cfaed&token=2016679640&lang =zh_CN#rd)

public class EncodedResource implements InputStreamSource {
    private final Resource resource;
    @Nullable
    private final String encoding;
    @Nullable
    private final Charset charset;
..........
..........
  public Reader getReader() throws IOException {
        if(this.charset != null) {
            return new InputStreamReader(this.resource.getInputStream(), this.charset);
        }
        else if(this.encoding != null) {
            return new InputStreamReader(this.resource.getInputStream(), this.encoding);
        }
        else {
            return new InputStreamReader(this.resource.getInputStream());
        }
    }
}

It is just a simple Wrapper class that returns different Readers for different character sets and character encodings

loadBeanDefinitions(EncodedResource)

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {

        //Get the resource being loaded from Thread Local
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

        //Determine whether this resource has been loaded, mainly for whether it is a circular dependency of the resource import
        if(!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException("");
        }

        try(InputStream inputStream = encodedResource.getResource().getInputStream()) {
            InputSource inputSource = new InputSource(inputStream);
            //Set it with encode
            if(encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            //real loading
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        catch(IOException ex) {
            throw new BeanDefinitionStoreException("");
        }
        finally {
            //Best practice of ThreadLocal
            currentResources.remove(encodedResource);
            if(currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

First get the loading Resource from ThreadLocal, here is mainly to check the circular reference problem brought by the import tag.

From here we can see that in finally, remove the resource that has been loaded, and check whether Set has any elements. If not, call the Remove method of ThreadLocal directly. This is the best practice of ThreadLocal. The final call of the remove method can avoid the memory leak caused by ThreadLocal as WeakReference in ThreadLocalMap.

What is basically done in this method, the most important thing is to call the doLoadBeanDefinitions method, and this method is really working.(In Spring, it is very interesting that the prefixes of the real work methods are all with do, this can be noted)

doLoadBeanDefinitions(InputSource Resource)

//Get the document object
Document doc = doLoadDocument(inputSource, resource);
//Register bean definition
int count = registerBeanDefinitions(doc, resource);
return count;

doLoadDocument This method is to convert Resource into Document, which involves xml file to verify, establish the corresponding Document Node, and use the above-mentioned DocumentLoader. This will not be discussed.

We go directly to the registerBeanDefinitions method

registerBeanDefinitions(Document,Resource)

public int registerBeanDefinitions(Document doc, Resource resource) {
        //Create a bean definition reader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        //The number of bean definitions that have been registered before return this.beanDefinitionMap.size();
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount()-countBefore;
    }

In the above code, there is a BeanDefinitionDocumentReader component that we mentioned. Its function is to read the Document and register it with BeanDefinitionRegistry

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   doRegisterBeanDefinitions(doc.getDocumentElement());
}

Here again, do is the real brother

protected void doRegisterBeanDefinitions(Element root) {

   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createDelegate(getReaderContext(), root, parent);

   if(this.delegate.isDefaultNamespace(root)) {
      //Handle profiles
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      if(StringUtils.hasText(profileSpec)) {
         String[]specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
         if(!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            return;
         }
      }
   }
   //Processing before explanation Here defaults to empty implementation, subclasses can override this method to do something before explaining Element
   preProcessXml(root);
   //Explanation
   parseBeanDefinitions(root, this.delegate);
   //Explain the post-processing here by default is empty
   postProcessXml(root);

   this.delegate = parent;
}

The main method here is parseBeanDefinitions

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {

   if(delegate.isDefaultNamespace(root)) {
      NodeList nl = root.getChildNodes();
      for(int i = 0; i <nl.getLength(); i++) {
         Node node = nl.item(i);
         if(node   instanceof Element) {
            Element ele =(Element) node;
            if(delegate.isDefaultNamespace(ele)) {
               //Spring default label explanation
               parseDefaultElement(ele, delegate);
            } else {
               //Custom label explanation
               delegate.parseCustomElement(ele);
            }
         }
      }
   } else {
      delegate.parseCustomElement(root);
   }
}

Spring's default tags are import, beans, bean, alias

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   //Explain the import tag
   if(delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   } else if(delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   } else if(delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
   } else if(delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      doRegisterBeanDefinitions(ele);
   }
}

Explain that the import tag call importBeanDefinitionResource will eventually be called into the method loadBeanDefinitions that we initially deal with the Resource circular dependency

We directly enter the processAliasRegistration method

protected void processAliasRegistration(Element ele) {
   String name = ele.getAttribute(NAME_ATTRIBUTE);
   String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
   boolean valid = true;
   if(!StringUtils.hasText(name)) {
      getReaderContext().error("Name must not be empty", ele);
      valid = false;
   }
   if(!StringUtils.hasText(alias)) {
      getReaderContext().error("Alias   must not be empty", ele);
      valid = false;
   }
   if(valid) {
      try {
        //The most important line of code
         getReaderContext().getRegistry().registerAlias(name, alias);
      } catch(Exception ex) {
         getReaderContext().error("Failed to register alias'" + alias +
               "'for bean with name'" + name + "'", ele, ex);
      }
      getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
   }
}

The most important line of code is to register name and alias(here is the relationship between the name and alias in the alias tag), you can refer to this article to understand [Spring-AliasRegistry]( https://mp.weixin.qq.com/s?__biz=MzUwOTk0MTA3MQ==&mid=2247483757&idx=1&sn=47636382c472962e2cf7641c01e7b1e9&chksm=f90bc54dce7c4c5bb8c39a7eaebfe6bf66a633da8

We come to the main processBeanDefinition

protected void processBeanDefinition(Element ele,
BeanDefinitionParserDelegate delegate) {

        //got a BeanDefinitionHolder here
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

        if(bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            } catch(BeanDefinitionStoreException ex) {
                .....
            }
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }

Let's analyze the code of BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())

public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        //Register bean name
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        //Register alias.
        String[]aliases = definitionHolder.getAliases();
        if(aliases != null) {
            for(String alias:aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }

The function of this method is very simple, is to use the BeanDefinitionRegistry that we passed to the XmlBeanDefinitionReader at the beginning to register the relationship between bean and beanDefinition. And also register the relationship between beanName and alias(here is to configure the relationship between the id and name attributes configured in the bean tag)

delegate.parseBeanDefinitionElement(ele) Let's return to this method, this method is where the BeanDefinition is created

@Nullable
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
        return parseBeanDefinitionElement(ele, null);
    }

@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {

   String id = ele.getAttribute(ID_ATTRIBUTE);
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

   List<String> aliases = new ArrayList<>();
   //Determine if the name attribute is configured and split the name
  //In bean tag, name is alias
   if(StringUtils.hasLength(nameAttr)) {
      String[]nameArr = StringUtils.tokenizeToStringArray(...);
      aliases.addAll(Arrays.asList(nameArr));
   }

   String beanName = id;
   //No id is configured and the alias list is not empty, the first alias is selected as the bean name
   if(!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
      beanName = aliases.remove(0);
   }

   if(containingBean == null) {
     //Check the uniqueness of beanName and alias
      checkNameUniqueness(beanName, aliases, ele);
   }

   //How to generate a BeanDefinition
   AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

   if(beanDefinition != null) {
      //If beanName is empty
      if(!StringUtils.hasText(beanName)) {
         try {
            if(containingBean != null) {
               beanName = BeanDefinitionReaderUtils.generateBeanName(
                     beanDefinition, this.readerContext.getRegistry(), true);
            } else {
               //If beanName and alias are not configured, then the first instance of this class will have the alias of the full class name
               //org.springframework.beans.testfixture.beans.TestBean is an alias(TestBean#0 only has this alias, others are not worthy to own)
               //org.springframework.beans.testfixture.beans.TestBean#0 This is beanName
               beanName = this.readerContext.generateBeanName(beanDefinition);

               String beanClassName = beanDefinition.getBeanClassName();
               if(beanClassName != null &&
                     beanName.startsWith(beanClassName) && beanName.length()> beanClassName.length() &&
                     !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                  aliases.add(beanClassName);
               }
            }

         } catch(Exception ex) {
           .........
         }
      }
      String[]aliasesArray = StringUtils.toStringArray(aliases);
      return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
   }
   //nothing
   return null;
}

In the bean tag, the name attribute corresponds to alias, and the id attribute corresponds to beanName.

When we do not configure the id attribute but configure the name attribute, then the first name attribute will become our id

When we neither configure the id attribute nor the name attribute, then Spring will help us generate specifics. [Spring-AliasRegistry]( https://mp.weixin.qq.com/s?__biz=MzUwOTk0MTA3MQ==&mid =2247483757&idx=1&sn=47636382c472962e2cf7641c01e7b1e9&chksm=f90bc54dce7c4c5bb8c39a7eaebfe6bf66a6334a8e8dd0d4244ae7333466a98158e711cc367c&token=2016679640&lang=zh_CN#

Then created a BeanDefinitionHolder and returned

In the above code, we see that there is this key method parseBeanDefinitionElement(ele, beanName, containingBean) This method generates the BeanDefinition we expect, but the content inside is relatively boring

//Explain the class attribute
String className = null;
if(ele.hasAttribute(CLASS_ATTRIBUTE)) {
  className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
//Whether the parent bean is specified
String parent = null;
if(ele.hasAttribute(PARENT_ATTRIBUTE)) {
  parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
  //Create GenericBeanDefinition
  AbstractBeanDefinition bd = createBeanDefinition(className, parent);
  //Explain various default attributes
  parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
  //extract describe
  bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
  //Interpret metadata
  parseMetaElements(ele, bd);
  //look up method
  parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
  //replacer
  parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
  //Parse the constructor parameters
  parseConstructorArgElements(ele, bd);
  //Explain the property child element
  parsePropertyElements(ele, bd);
  //explain qualifier
  parseQualifierElements(ele, bd);

  bd.setResource(this.readerContext.getResource());
  bd.setSource(extractSource(ele));

Is to parse various attributes in the bean tag

Then our entire Spring container initialization process is introduced

to sum up

public static void main(String[]args) {
        Resource resource = new ClassPathResource("coderLi.xml");
        DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
        xmlBeanDefinitionReader.loadBeanDefinitions(resource);
    }
  1. Call the method loadBeanDefinitions of XmlBeanDefinitionReader

  2. Wrap Resource into EncodeResource

  3. Use ThreadLocal to determine whether Resource is circularly dependent

  4. Use DocumentLoader to convert Resource to Document

  5. Use BeanDefinitionDocumentReader to interpret Document tags

  6. Explain the default tags/custom tags provided by Spring

    • Call back to step 2 when explaining the import tag
    • Explain that the alias tag will be registered with AliasRegistry
    • Explain that the bean tag will register the beanName and BeanDefinition with the BeanDefinitionRegistry, and will also register the relationship between the id and name in the bean tag(actually alias)

The general process is like this. During the interview, roughly say XmlBeanDefinitionReader, DocumentLoader, BeanDefinitionDocumentReader, BeanDefinitionRegistry, AliasRegistry. The interviewer will most likely think that you have really seen this part of Spring s code.

Past Articles

[Spring-resource loading(source code analysis)]( https://mp.weixin.qq.com/s?__biz=MzUwOTk0MTA3MQ==&mid=2247483828&idx=1&sn=6d8902e3ae729b8f9d7b5af992bf595b&chksm=f90bc594cedfbd6d3d3d8

[Spring-AliasRegistry]( https://mp.weixin.qq.com/s?__biz=MzUwOTk0MTA3MQ==&mid=2247483757&idx=1&sn=47636382c472962e2cf7641c01e7b1e9&chksm=f90bc54dce7c4c5bb8a39a7a66a8

[Compiled Spring5.2.0 source code]( https://mp.weixin.qq.com/s?__biz=MzUwOTk0MTA3MQ==&mid=2247483697&idx=1&sn=c6e218d7299d94e1ae8fecfde534c589&chksm=f90bc511ce7c4c078560393b232a793a33003

Must be this time?