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); }
- Call the method loadBeanDefinitions of XmlBeanDefinitionReader
- Package Resource as EncodeResource
- Use ThreadLocal to determine whether Resource circular dependency exists
- Use DocumentLoader to convert Resource to Document
- Use BeanDefinitionDocumentReader to interpret the label of a Document
-
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