XML load parsing
1. Load Bean definition
1.1 parsing XML documents
XmlBeanDefinitionReader#loadBeanDefinitions
/** * Load xml resources, parse all xml tags, encapsulate the bean corresponding to xml as beandefinition (the real type is GenericBeanDefinition), and register * Returns the number of loaded beandefinitions */ public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { // EncodedResource adds encoding and charset attributes Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } // Record the currently loaded xml. Loading if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } // Get the configuration resource input file stream object (xml file input stream) InputStream inputStream = encodedResource.getResource().getInputStream(); // org.xml.sax.InputSource // Encapsulate InputSource to prepare for xml parsing InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // Real processing return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); .... finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { // Load end delete this.resourcesCurrentlyBeingLoaded.remove(); } }
This method is the top-level method for loading xml. First, it is connected to the XML configuration file, then the file is encapsulated as InputSource, and then the real processing of XML is handed over to the doloadbean definitions method.
XmlBeanDefinitionReader#doLoadBeanDefinitions
/** * Further split the task * Parsing and encapsulating xml files into documents * Then register the bean information according to the Document * @param inputSource * @param resource * @return * @throws BeanDefinitionStoreException */ protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { // Get the validation schema for the xml file and load the xml file as document Document doc = doLoadDocument(inputSource, resource); // Register Bean information according to the returned Document return registerBeanDefinitions(doc, resource); } ... }
This method further splits the task. DefaultDocumentLoader#loadDocument parses the xml stream into a Document object. XmlBeanDefinitionReader#registerBeanDefinitions further parses and registers bean information according to the Document object.
XmlBeanDefinitionReader#doLoadDocument
/** * Parsing XML * Allows you to include XML fragments into application context definitions using standard XML entities, such as splitting large XML files into modules */ protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { // getValidationModeForResource gets the validation mode (1, DTD, 2, xsd) of the XML file. If it is not set, the stream automatic judgment mode will be loaded // getEntityResolver XML parser return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
XmlBeanDefinitionReader#registerBeanDefinitions registers each bean definition in the given root element through DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions.
protected void doRegisterBeanDefinitions(Element root) { .... if (this.delegate.isDefaultNamespace(root)) { // Multiple environments can be configured by handling the profile property of beans 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)) { if (logger.isInfoEnabled()) { logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } // Preprocessing is left to subclasses preProcessXml(root); // Real parsing bean definitions parseBeanDefinitions(root, this.delegate); // Post processing is left to subclasses postProcessXml(root); .... }
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
/** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */ protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // Processing beans 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) { // Processing bean s Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { // Process the default http //w ww.springframework org/scherna beans parseDefaultElement(ele, delegate); } else { // Handle customized ones, such as those under tx delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
At this point, start to cycle and parse the sub tags according to the root (beans) node. The sub tags are divided into default tags and user-defined tags. These two processing methods are very different. The following are all about the default label, and the user-defined label is described separately.
Tasks completed here:
- The xml resource resource is loaded and encapsulated as encoded resource (charset can be set).
- Encapsulate the XML resource input stream object as an InputSource for the DocumentLoader to load and parse into a Document object (you need to specify an XML parser and which XML format is the standard mode (xsd|dtd)).
- Get the root node through the Document to start parsing. Before that, it can be processed by the pre processor (root), and after that, it can be processed by the post processor (root).
- Loop parsing all sub tags under root. The tags are divided into default tags and user-defined tags. The processing is very different. Default tags, such as bean and import, and custom tags, such as tx.
1.2 parsing default labels
DefaultBeanDefinitionDocumentReader#parseDefaultElement
/** * beans.xsd By default, there are four types of import beans beans alias * @param ele * @param delegate */ private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // import type processing // Yes, plus is to recursively call the bean method of loading the configuration file definition // XMLBeanDefinitionReader.loadBeanDefinitions(resource) if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // alias processing // The processing is the same as that of the name attribute in the bean else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // bean processing else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } // beans processing // Nesting without check-in is no big difference. Call the beans parsing process in a loop else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
This method handles each default tag differently. In fact, both import and beans call the parsing process recursively, so it mainly depends on alias and bean.
bean tag parsing
DefaultBeanDefinitionDocumentReader#processBeanDefinition
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // Resolve various default properties under the bean and store them in the bdholder object BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { // If bdholder is not empty, check whether there are custom attributes and resolve them bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); // After parsing, bdHolder will have all the information of the bean tag try { // Then register in the delegate BeanDefinitionReaderUtils.registerBeanDefinition // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Notify this bean that registration is complete // For future expansion, it is not implemented at present getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
In this method, BeanDefinitionParserDelegate.parseBeanDefinitionElement parses the default tag and saves the parsed information to BeanDefinitionHolder
BeanDefinitionParserDelegate#parseBeanDefinitionElement
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { // Parse id String id = ele.getAttribute(ID_ATTRIBUTE); // Resolve name String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // Split name List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; // id is null, use the first of name if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); ... } // Check the uniqueness of beanName in the bean container if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } // Further parsing all other attributes of a bean is to parse all attributes and elements in a bean into JavaBeans, that is, AbstractBeanDefinition // There are AbstractBeanDefinition classes for beans in xml AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { ... // beanname does not exist. The beanname is generated for the current bean based on the naming rules provided by Spring if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { 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); } } ... } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
In this method, the attributes of the bean tag (only id and name are resolved, and others are resolved in the following methods). The resolution of the sub tag is handed over to the lower level for processing (BeanDefinitionParserDelegate#parseBeanDefinitionElement), and the resolution of the custom tag under the bean is processed separately.
BeanDefinitionParserDelegate#parseBeanDefinitionElement
/** * Parse the bean definition itself, without regard to name or aliases. May return * {@code null} if problems occurred during the parsing of the bean definition. */ @Nullable public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { // A collection (LinkedList) will the beanname record being parsed this.parseState.push(new BeanEntry(beanName)); // Parse class String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } // Parse parent String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } ... // Create a GenericBeanDefinition to host the property AbstractBeanDefinition type // BeanDefinition is the internal representation of the configuration file < bean > element label in the container // The < bean > element tag has //Class scope lazy init and other configuration attributes. BeanDefinition provides the corresponding beanclass scope lazy init //Attribute. The attribute in beandefinition < bean > is a corresponding attribute // Spring converts the < bea > configuration information in the configuration file into the internal representation of the container through BeanDefinition //These beandefinitions are registered in beandefinitionregistry. Beandefinitionregistry for Spring container γ Like //The memory database of Spring configuration information is mainly saved in the form of map. Subsequent operations directly read the configuration information from the BeanDefinition Registry AbstractBeanDefinition bd = createBeanDefinition(className, parent); // Hard coded parsing of various default properties of the default bean parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // The following is all the analysis of child elements // Extract description bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // Parsing meta metadata child elements // Take out all meta sub elements, encapsulate the key value into a BeanMetadataAttribute object (including) and save it to beanmetadatattributeaccessor // AbstractBeanDefinition integrates BeanMetadataAttributeAccessor parseMetaElements(ele, bd); // Parsing lookup method // The abstract method returns the specified bean < lookup method name = "getBean" bean = "bean" > getBean is an abstract method parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // Resolve replaced method // The difference between the lookup method and the lookup method is that the lookup method customizes the returned bean s, and the replaced method can replace the logic of the method // Will be encapsulated as methodOverrides and stored in the methodOverrides of bd parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // Resolve constructor parameters parseConstructorArgElements(ele, bd); // Resolving property child elements parsePropertyElements(ele, bd); // Resolve the qualifier child element to specify beanName disambiguation parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; ... return null; }
This method first parses the class and parent of the bean tag, then creates a default AbstractBeanDefinition, then parses various default attributes of the bean through BeanDefinitionParserDelegate#parseBeanDefinitionAttributes, and then parses other sub tags (such as lookup method, property, constructor, etc.) in this method.
BeanDefinitionParserDelegate#parseBeanDefinitionAttributes
/** * Applies the attributes of a given bean element to a given bean definition * Apply the attributes of the given bean element to the given bean * definition. * @param ele bean declaration element * @param beanName bean name * @param containingBean containing bean definition * @return a bean definition initialized according to the bean element attributes */ public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) { // Parsing singleton using singleton will prompt an error if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele); } // Parse scope else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); } // When beanDifinition is embedded and the scope attribute is not specified separately, the default attribute of the parent class is used else if (containingBean != null) { // Take default from containing bean in case of an inner bean definition. bd.setScope(containingBean.getScope()); } // Parse abstract if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); } // Parsing lazy init String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); if (isDefaultValue(lazyInit)) { lazyInit = this.defaults.getLazyInit(); } // Non true strings or empty strings will be false bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); // Parsing autowire String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); bd.setAutowireMode(getAutowireMode(autowire)); // Parsing dependencies on if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS)); } // Resolve autowire candidate attribute String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); if (isDefaultValue(autowireCandidate)) { String candidatePattern = this.defaults.getAutowireCandidates(); if (candidatePattern != null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); } } else { bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); } // Resolve primary attribute if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); } // Parsing init method if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) { String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); bd.setInitMethodName(initMethodName); } else if (this.defaults.getInitMethod() != null) { bd.setInitMethodName(this.defaults.getInitMethod()); bd.setEnforceInitMethod(false); } // Parse destroy method if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) { String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); bd.setDestroyMethodName(destroyMethodName); } else if (this.defaults.getDestroyMethod() != null) { bd.setDestroyMethodName(this.defaults.getDestroyMethod()); bd.setEnforceDestroyMethod(false); } // Parse factory method if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); } // Parse factory bean if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); } return bd; }
So far, the default label parsing of a bean is completed, and all the information of the bean label in xml is encapsulated in the AbstractBeanDefinition object.
The parsing of custom tags under bean s will be described separately
After parsing the default tag and custom tag of the bean in the DefaultBeanDefinitionDocumentReader#processBeanDefinition, you need to register the object (BeanDefinitionHolder) that holds the bean definition information into the Spring container through the DefaultListableBeanFactory#registerBeanDefinition method.
@Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { ... if (beanDefinition instanceof AbstractBeanDefinition) { try { // The last verification before registration. The verification here is different from the previous XML file verification, // It is mainly used to verify methodOverrides in AbstractBeanDefinition // Check whether methodOverrides coexist with factory methods or the method corresponding to methodOverrides does not exist at all ((AbstractBeanDefinition) beanDefinition).validate(); } ... } // Thread safe map, so there is no need to use synchronized here BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); // The bean queried by beanName already exists if (existingDefinition != null) { // A series of judgments // Allow Bean definition overrides check whether overrides are allowed // If the corresponding BeanName has been registered and the bean configured in the configuration is not allowed to be overwritten, an exception is thrown // Is the current object to be saved the same as the one to be taken out // Register beanDefinition this.beanDefinitionMap.put(beanName, beanDefinition); } else { // Is the bean marked as created // Full replication required if (hasBeanCreationStarted()) { // Cannot modify startup-time collection elements anymore (for stable iteration) synchronized (this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); 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 { // Still in startup registration phase this.beanDefinitionMap.put(beanName, beanDefinition); // Record bean name this.beanDefinitionNames.add(beanName); this.manualSingletonNames.remove(beanName); } this.frozenBeanDefinitionNames = null; } // The bean is overridden by a new definition or is a singleton if (existingDefinition != null || containsSingleton(beanName)) { // Resets all bean definition caches for a given bean, including those derived from it resetBeanDefinition(beanName); } else if (isConfigurationFrozen()) { clearByTypeCache(); } }
At this point, the bean parsing and registration are all completed
In addition to resolving several specific attributes, import is a recursive call to the beans parsing process.
The case where beans are included under beans is also the parsing process of recursively calling beans.
Alias mainly resolves the default attribute and records the association between alias and beanname.
Completed tasks:
-
Resolve different sub tags under root (import beans alias)
-
Resolve all default attributes (class, id, etc.) of the bean tag
-
Resolve all sub tags of the bean tag (property, etc.)
-
Resolve all custom tags under the bean
-
Register the AbstractBeanDefinition object that carries all the information of the bean tag in the xml into the Spring container (in fact, it is a map, which is saved in it)
Note: the resolution of custom tags is not described separately.
---- **Cover <Spring In depth analysis of source code**