Preface
Following the previous article Spring Decryption - XML parsing and Bean registration, let's continue to analyze the source code. Without further ado, let's take a look at the detailed introduction together.
Decryption
There are two major categories of declarations in Spring's XML configuration. One is the default one, such as <bean id="person"/> , and the other is the custom one, such as <tx:annotation-driven /> . The two tags have very different parsing methods. The parseBeanDefinitions method is used to distinguish the parse methods used by different tags. Get the namespace through node.getNamespaceURI() method, determine whether it is a default namespace or a custom namespace, and compare it with the fixed namespace http://www.springframework.org/schema/beans in Spring. If it is consistent, use parseDefaultElement(ele, delegate); otherwise, it is delegate.parseCustomElement(ele);
Parsing of default tags
parseDefaultElement has done different processing for 4 different tags import, alias, beans, and beans. Among them, the analysis of bean tags is the most complex and important, so we will start in-depth analysis from the bean. If we can understand the analysis process of this tag, the analysis of other tags will naturally be solved. In the previous article, we just briefly describe it. In this article, we will discuss it in detail around the analysis module.
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // import tag parsing if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // alias tag parsing else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // bean tag resolution else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } // import tag resolution else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans tag resolution recursive method doRegisterBeanDefinitions(ele); } }} First, let’s analyze processBeanDefinition(ele, delegate) in the class
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // Delegate the parseBeanDefinitionElement method of the BeanDefinitionDelegate class for element parsing BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { // When the returned bdHolder is not empty, if there is a custom attribute under the child node of the default label, you need to parse the custom label again bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // After the parsing is completed, the parsed bdHolder needs to be registered. The registration operation is delegated to the registerBeanDefinition method of BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Finally, a response event is issued to notify the relevant listener that the bean has been loaded getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); }}In this code:
Let's analyze in detail how Spring parses each tag and node
bean tag analysis
public class BeanDefinitionParserDelegate { @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containsBean) { // Get the ID attribute of the Bean tag String id = ele.getAttribute(ID_ATTRIBUTE); // Get the Name attribute of the Bean tag String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { // Pass the value of the name attribute, and split it into a string number (that is, if multiple names are configured in the configuration file, process it here) String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; // If the ID is empty, use the configured first name attribute as the ID if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { // Verify the uniqueness of beanName and aliases// The internal core is to use the usedNames collection to save all used beanName and aliasesUniqueness(beanName, aliases, ele); } // Further parse all other properties into the GenericBeanDefinition object AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containedBean); if (beanDefinition != null) { // If the bean does not specify beanName, then use the default rule to generate beanName for this bean if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); // Encapsulate information into the BeanDefinitionHolder object return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }} This method mainly processes related attributes such as id, name, alias, etc., generates beanName, and completes the core tag parsing in the overload function parseBeanDefinitionElement(ele, beanName, containingBean) method.
Next, focus on parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean)
See how it completes the tag parsing operation
Bean node and attribute analysis
@Nullablepublic AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containsBean) { this.parseState.push(new BeanEntry(beanName)); // Get the class attribute of the bean tag String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } // Get the parent attribute of the bean tag String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { // Create AbstractBeanDefinition to host attributes AbstractBeanDefinition bd = createBeanDefinition(className, parent); // Get various attributes of bean tag parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // parse description tag bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // parse meta tag parseMetaElements(ele, bd); // parse lookup-method tag parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // parse replaced-method tag parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // parse replaced-method tag parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // parse constructor-arg tag parseConstructorArgElements(ele, bd); // parse property tag parsePropertyElements(ele, bd); // parse qualifier tag parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null;}Further parse other attributes and elements (there are many elements and attributes, so this is a huge workload) and package them into GenericBeanDefinition. After parsing these attributes and elements, if you detect that the bean does not have a specified beanName, then you use the default rules to generate a beanName for the bean.
// BeanDefinitionParserDelegate.javaprotected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName) throws ClassNotFoundException { return BeanDefinitionReaderUtils.createBeanDefinition( parentName, className, this.readerContext.getBeanClassLoader());}public class BeanDefinitionReaderUtils { public static AbstractBeanDefinition createBeanDefinition( @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { GenericBeanDefinition bd = new GenericBeanDefinition(); // parentName may be empty bd.setParentName(parentName); // If classLoader is not empty// Then use the passed classLoader to load the class object with the same virtual machine. Otherwise, only classLoader is recorded if (className != null) { if (classLoader != null) { bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } return bd; }}BeanDefinition is an internal representation of <bean> in a container, and BeanDefinition and <bean> are one-to-one. At the same time, BeanDefinition will be registered in BeanDefinitionRegistry, which is like the in-memory database of Spring configuration information.
So far, createBeanDefinition(className, parent); has been finished, and we have also obtained the AbstractBeanDefinition used to host the attributes. Let's take a look at how parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); parse various tag attributes in beans.
public class BeanDefinitionParserDelegate { public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containsBean, AbstractBeanDefinition bd) { // ...Omit the detailed code, this part of the code mainly determines whether it contains the specified attribute through if else. If there is, bd.set(attribute); return bd; }}```The complete parsing of the `bean` tag has ended. The element parsing under the `bean` tag is similar. If you are interested, you can track the source code and see the parsing methods such as `qualifier, lookup-method` (*not complicated than `bean`*). The custom tag content is more detailed in the next chapter.
Finally, encapsulate the obtained information into the `BeanDefinitionHolder` instance
``` java// BeanDefinitionParserDelegate.java@Nullablepublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containsBean) { // ... return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}Register a parsed BeanDefinition
After parsing the configuration file, we have obtained all the properties of the bean, and the next step is to register the bean
public class BeanDefinitionReaderUtils { public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // Use beanName as a unique identifier String beanName = definitionHolder.getBeanName(); // Register the core code of the bean registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register all aliases for the bean String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }}The above code mainly completes two functions: one is to register beanDefinition using beanName, and the other is to complete the registration of alias.
BeanName Register BeanDefinition
public class DefaultListableBeanFactory { @Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { // The last verification before registration, the verification here is different from the XML file verification // It is mainly for the methodOverrides verification in the AbstractBeanDefinition property // Check whether methodOverrides coexist with the factory method or the methodOverrides does not exist at all ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } BeanDefinition oldBeanDefinition; // Get the beanDefinition in the cache oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition != null) { // If there is a cache, determine whether overwriting is allowed if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + "': There is already [" + oldBeanDefinition + "] bound."); } else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) { // eg was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE if (this.logger.isWarnEnabled()) { this.logger.warn("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } else if (!beanDefinition.equals(oldBeanDefinition)) { if (this.logger.isInfoEnabled()) { this.logger.info("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } // If overwrite is allowed, save beanDefinition into beanDefinitionMap this.beanDefinitionMap.put(beanName, beanDefinition); } else { // Determine whether bean creation has started if (hasBeanCreationStarted()) { // Cannot modify startup-time collection elements anymore (for stable iteration) synchronized (this.beanDefinitionMap) { // Save beanDefinition into beanDefinitionMap this.beanDefinitionMap.put(beanName, beanDefinition); // Update registered beanName List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); updatedDefinitions.addAll(this.beanDefinitionNames); updatedDefinitions.add(beanName); this.beanDefinitionNames = updatedDefinitions; if (this.manualSingletonNames.contains(beanName)) { Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames); updatedSingletons.remove(beanName); this.manualSingletonNames = updatedSingletons; } } } else { // I haven't started creating beans yet this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName); this.manualSingletonNames.remove(beanName); } this.frozenBeanDefinitionNames = null; } if (oldBeanDefinition != null || containsSingleton(beanName)) { // Reset the cache corresponding to beanName resetBeanDefinition(beanName); } }}Register an alias
After registering beanDefinition, the next step is to register alias. The corresponding relationship between registered alias and beanName is stored in aliasMap. You will find that the registerAlias method is implemented in SimpleAliasRegistry
public class SimpleAliasRegistry { /** Map from alias to canonical name */ private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16); public void registerAlias(String name, String alias) { Assert.hasText(name, "'name' must not be empty"); Assert.hasText(alias, "'alias' must not be empty"); if (alias.equals(name)) { // If the beanName is the same as alias, alias will not be recorded and delete the corresponding alias this.aliasMap.remove(alias); } else { String registeredName = this.aliasMap.get(alias); if (registeredName != null) { if (registeredName.equals(name)) { // If the alias has been registered and the name pointed to is the same as the current name, no processing will be done; } // If alias does not allow overwriting, an exception will be thrown if (!allowAliasOverriding()) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } } // The check loop points to dependencies such as A->B B->C C->A, an error occurs checkForAliasCircle(name, alias); this.aliasMap.put(alias, name); } }} Check the alias circular dependency through the checkForAliasCircle() method. When A -> B exists, if A -> C -> B appears again, an exception will be thrown:
protected void checkForAliasCircle(String name, String alias) { if (hasAlias(alias, name)) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': Circular reference - '" + name + "' is a direct or indirect alias for '" + alias + "' already"); }}public boolean hasAlias(String name, String alias) { for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) { String registeredName = entry.getValue(); if (registeredName.equals(name)) { String registeredAlias = entry.getKey(); return (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)); } } return false;}At this point, the registration of alias has been completed, and the following tasks have been completed.
Send notification
Notify the listener to be parsed and registered
//DefaultBeanDefinitionDocumentReader.javaprotected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}The fireComponentRegistered method is used to notify the listener to parse and register the work. The implementation here is only for extension. When the program developer needs to listen to the registered BeanDefinition event, he can register the listener and write the processing logic into the listener. Currently, Spring does not handle this event in this event
The ReaderContext is generated by calling createReaderContext in the class XmlBeanDefinitionReader, and then calling fireComponentRegistered()
alias tag analysis
Spring provides alias configuration for <alias name="person" alias="p"/> . The tag resolution is done in the processAliasRegistration(Element ele) method.
public class DefaultBeanDefinitionDocumentReader { protected void processAliasRegistration(Element ele) { // Get alisa tag name property String name = ele.getAttribute(NAME_ATTRIBUTE); // Get alisa tag alisa tag alias 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 { // Register alias getReaderContext().getRegistry().registerAlias(name, alias); } catch (Exception ex) { getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, ex); } // After registering the alias, tell the listener to do the corresponding processing getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); } }}First, the alias tag attribute is extracted and verified. After the verification is passed, alias registration is carried out. Alias registration and alias registration in bean tag analysis have been done. I will not repeat it here.
import tag analysis
public class DefaultBeanDefinitionDocumentReader { protected void importBeanDefinitionResource(Element ele) { // Get the resource attribute of the import tag String location = ele.getAttribute(RESOURCE_ATTRIBUTE); // If it does not exist, no processing is done if (!StringUtils.hasText(location)) { getReaderContext().error("Resource location must not be empty", ele); return; } // Parsing placeholder attribute format such as "${user.dir}" location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location); Set<Resource> actualResources = new LinkedHashSet<>(4); // Determine whether the resource is an absolute path or a relative path boolean absoluteLocation = false; try { absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); } catch (URISyntaxException ex) { // cannot convert to an URI, considering the location relative // unless it is the well-known Spring prefix "classpath*:" } // If it is an absolute path, the corresponding configuration file will be loaded directly according to the address if (absoluteLocation) { try { int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); if (logger.isDebugEnabled()) { logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]"); } } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, ex); } } else { try { int importCount; // Load the resource according to the relative path Resource relativeResource = getReaderContext().getResource().createRelative(location); if (relativeResource.exists()) { importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource); actualResources.add(relativeResource); } else { String baseLocation = getReaderContext().getResource().getURL().toString(); importCount = getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources); } if (logger.isDebugEnabled()) { logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]"); } } catch (IOException ex) { getReaderContext().error("Failed to resolve current resource location", ele, ex); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, ex); } } // After parsing, listener activation processing is performed. Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]); getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); }} After completing the processing of the import tag, the first thing is to obtain the path represented by the <import resource="beans.xml"/> resource attribute, then parse the attribute placeholder in the path such as ${user.dir} , and then determine whether the location is an absolute path or a relative path. If it is an absolute path, the bean's parsing process is called recursively (loadBeanDefinitions(location, actualResources);) to perform another analysis. If it is a relative path, calculate the absolute path and parse it. Finally, notify the listener and the parsing is completed.
Summarize
After a few unknown autumn, winter, spring and summer, everything will follow the direction you want...
Okay, the above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.
Say something
Full text code: https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter1 (local download)