Initialization of Spring container

Keywords: Java Spring Attribute xml encoding

After reading this article, you will get

  • Learn about the Spring container initialization process
  • The best practice of ThreadLocal in Spring
  • Answer the Spring container initialization process in 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 A kind of For a second? This time
        </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 Java code above mainly does

  • Acquisition (positioning) of resources
  • Create a beanFactory
  • Create a beanDefinitionReader based on beanFactory, which implements the BeanDefinitionRegistry interface
  • Load the resource and register the beanDefinition in the resource

So in general, it is the three steps of loading, loading and registering resources

  • For the loading of resources, see my other article Spring resource loading (source code analysis)
  • The process of loading is to turn the Resource object into a series of BeanDefinition objects
  • Registration is to inject BeanDefinition into BeanDefinitionRegistry

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, which is the default implementation of bean registration and loading

For alias registry, please refer to another article Spring-AliasRegistry . We just need to remember two points about this class, one is that it is a beanFactory, the other is that it is a BeanDefinitionRegistry

XmlBeanDefinitionReader

Read from XML resource file and convert to BeanDefinition functions

DocumentLoader

Convert Resource files to Document files

BeanDefinitionDocumentReader

Read Document and register with BeanDefinitionRegistry

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)

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, which returns different readers for different character sets and character encodings

loadBeanDefinitions(EncodedResource)

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {

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

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

        try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
            InputSource inputSource = new InputSource(inputStream);
            // If you have encode, set it in
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // Real load
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException("");
        }
        finally {
            // ThreadLocal best practices
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

First, get the loaded Resource from ThreadLocal. Here we mainly check the circular reference problem caused by the import tag.

From here, we can see that in finally, we can remove the loaded resources and check whether there are any elements in the Set. If not, we can directly call the remove method of ThreadLocal. This is the best practice of ThreadLocal. The call of the last remove method can avoid the memory leak caused by ThreadLocal being used as WeakReference in ThreadLocalMap.

The basic thing to do in this method is to call the doLoadBeanDefinitions method, which is the real work. (in Spring, it's interesting that the real working method prefixes are all with do, which can be noticed)

doLoadBeanDefinitions(InputSource Resource)

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

The method of doLoadDocument is to convert resources into documents. It involves xml files to validation, establishing corresponding Document nodes, and using 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 reader for bean definition
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        // Number of bean definition s before registration 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 we mentioned. Its function is to read documents and register with BeanDefinitionRegistry

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

Here comes again. do is the real working brother

protected void doRegisterBeanDefinitions(Element root) {
  
   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createDelegate(getReaderContext(), root, parent);

   if (this.delegate.isDefaultNamespace(root)) {
      // Processing 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 interpretation is empty by default. Subclass can override this method to do something before Element is interpreted
   preProcessXml(root);
   // explain
   parseBeanDefinitions(root, this.delegate);
   // The default is empty implementation
   postProcessXml(root);

   this.delegate = parent;
}

The main method here is parsebean definitions

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 interpretation
               parseDefaultElement(ele, delegate);
            } else {
               // Custom label interpretation
               delegate.parseCustomElement(ele);
            }
         }
      }
   } else {
      delegate.parseCustomElement(root);
   }
}

Spring's default tags are import, beans, beans, and 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 the import tag, call importBeanDefinitionResource, and it will eventually call the method loadBeanDefinitions that we first dealt with Resource cycle dependency

We go directly to 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 (registered here is the relationship between name and alias in the alias tag). Please refer to this article for details Spring-AliasRegistry

We come to the main processbean definition

protected void processBeanDefinition(Element ele,
BeanDefinitionParserDelegate delegate) {
        
        // Here's a bean definitionholder 
        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 start with bean D efinitionReaderUtils.registerBeanDefinition (bdholder, getreadercontext(). Getregistry()) code

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. It uses the BeanDefinitionRegistry we passed to XmlBeanDefinitionReader in the beginning to register the relationship between bean and beanDefinition. And also register the relationship between bean name and alias (here is the configuration of the id and name attribute relationship configured in the bean tag)

delegate.parse BeanDefinition Element (ELE) let's go back to this method, which is where bean definition 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 whether the name attribute is configured and split the name 
  // In the bean tag, name is alias
   if (StringUtils.hasLength(nameAttr)) {
      String[] nameArr = StringUtils.tokenizeToStringArray(...);
      aliases.addAll(Arrays.asList(nameArr));
   }

   String beanName = id;
   // If there is no configuration id and the alias list is not empty, select the first alias as 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 bean definition Ni
   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 alias with full class name
               // org.springframework.beans.testfixture.beans.TestBean  This is an alias (testbean ා 0 owns this alias, and others don't deserve to own it)
               // org.springframework.beans.testfixture.beans.TestBean#0  This is bean name
               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 the name attribute, the first name attribute will become our id

When we do not configure the id attribute or the name attribute, Spring will help us generate the specific information Spring-AliasRegistry

Then a BeanDefinitionHolder is created and returned

In the above code, we see that the key method parseBeanDefinitionElement(ele, beanName, containingBean) has generated the expected BeanDefinition, but the content is relatively boring

// Explain class attribute
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
  className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
// Whether 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 properties
  parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
  // Extract description
  bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
  // Interpreting metadata
  parseMetaElements(ele, bd);
  // look up method
  parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
  // replacer
  parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
  // Resolving constructor parameters
  parseConstructorArgElements(ele, bd);
  // Interpreting property child elements
  parsePropertyElements(ele, bd);
  // Explain qualifier
  parseQualifierElements(ele, bd);

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

It's all about parsing the properties in the bean tag

Then the whole Spring container initialization process is introduced

summary

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. Package Resource as EncodeResource
  3. Use ThreadLocal to determine whether Resource circular dependency exists
  4. Use DocumentLoader to convert Resource to Document
  5. Use BeanDefinitionDocumentReader to interpret the label of a Document
  6. Explain the default tag / custom tag explanation provided by Spring

    • When interpreting the import tag, it will call back to step 2
    • Interpreting the alias tag registers with the alias registry
    • Explaining the bean tag will register bean name and BeanDefinition with BeanDefinitionRegistry, and also the relationship between id and name in the bean tag (in fact, alias)

This is the general process. During the interview, you can roughly say the following components: xmlbeandefinitionreader, documentloader, beandefinitiondocumentreader, beandefinitionregistry, and aliasregistry. The interviewer is likely to think that you have actually seen this part of Spring's code

Previous articles

Spring resource loading (source code analysis)

Spring-AliasRegistry

Compiling spring 5.2.0 source code

Posted by henryblake1979 on Fri, 05 Jun 2020 22:41:05 -0700