This article parses xml for Srping's xmlBeanFactory and registers the parsed information using GenericBeanDefinition as a carrier. xml BeanFactory has been marked as not recommended in Spring 3.1, but we do not analyze the source code because it has not changed, and Application Context still uses XmlBe. An Definition Reader and DefaultListable BeanFactory do xml analysis and registration. This blog is to follow the source code step by step to see how spring can register bean s. The source code is spring 5.X. The source code has been annotated on each line to facilitate readers'learning.
GitHub: https://github.com/lantaoGitH...
- First, we start with the XMLBeanFactory and code it directly:
package org.springframework.lantao; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; public class XmlBeanFactoryTest { public static void main(String[] args) { // Resource Loading ClassPathResource classPathResource = new ClassPathResource("spring-bean.xml"); // XmlBeanFactory loads resources and parses registered bean s BeanFactory beanFactory = new XmlBeanFactory(classPathResource); // BeanFactory.getBean(); UserBean userBean = (UserBean) beanFactory.getBean("userBean"); System.out.println(userBean.getName()); } }
- XmlBeanFactory parses Xml using the XmlBeanDefinitionReader.loadBeanDefinition() method with the following source code:
@Deprecated @SuppressWarnings({"serial", "all"}) public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); /** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource the XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { //Call the constructor on line 79 this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource the XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { //Ignore Dependency Interface ignores automatic assembly //The main function is that when there are overlooked interface classes, automatic assembly will overlook the initial assembly of this classification, because in some cases, the interface implementation classes can not be initialized at this time, such as BeanNameAware. If you want to assemble the implementation object of this interface, you can implement this interface. super(parentBeanFactory); //This code is a real resource load this.reader.loadBeanDefinitions(resource); } }
- Let's look directly at the loadBeanDefinition method, source code:
/** * Load bean definitions from the specified XML file. * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { // Encoded Resource is encapsulated, String encoding and Charset charset are set return loadBeanDefinitions(new EncodedResource(resource)); } /** * Load bean definitions from the specified XML file. * @param encodedResource the resource descriptor for the XML file, * allowing to specify an encoding to use for parsing the file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { //encodedResource cannot be empty Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } // Recording loaded resources by attributes Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { // Get InputStream from encodedResource encapsulated Resource InputStream inputStream = encodedResource.getResource().getInputStream(); try { //InputSource is not spring, but org.xml.sax InputSource inputSource = new InputSource(inputStream); //If Encoding in encodedResource is not null, set Encoding in InputSource synchronously if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //Definitions for loading bean s load information from xml into Definition, and key+definitions are registered in memory return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
The above source code may look long, but in fact, this is not the real place to parse. Here we do the following:
1: Get InputStream from encapsulated Resource of encodedResource;
2: If Encoding in encodedResource is not null, the Encoding of InputSource is set synchronously.
3: Delegate the parsing action to doLoadBean Definitions.
- Next, let's move on to the doLoadBean Definitions method content:
/** * Actually load bean definitions from the specified XML file. * @param inputSource the SAX InputSource to read from * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #doLoadDocument * @see #registerBeanDefinitions */ protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //Load Document Document doc = doLoadDocument(inputSource, resource); //Register bean s int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
When we look at this method, it is still not a real method of parsing or registering. Here we just load the Document and delegate the follow-up work to registerBean Definitions, the number of registerBean Definitions registered when the registerBean Definitions method returns.
- Let's continue with the source code for registerBean Definitions:
/** * Register the bean definitions contained in the given DOM document. * Called by {@code loadBeanDefinitions}. * <p>Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //Instantiate Bean Definition Document Reader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //Get the number of beanDefinition loads before int countBefore = getRegistry().getBeanDefinitionCount(); //Loading xml and registering bean s documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //Record the number of loads return getRegistry().getBeanDefinitionCount() - countBefore; }
The registerBean Definitions method is implemented concretely:
1: Instance BeanDefinition Document Reader by BeanUtils. instantiateClass (this. documentReader Class);
2: Get the number of registered beans by bean Definition Map. size () in DefaultListAbleBeanFactory. (Bean Definition Map is the carrier of storing the information after final xml parsing. The information after xml parsing is stored by GenericBean Definition. The storage format of bean Definition Map is key: String value: GenericBean Definit. Ion)
3: Delegate parsing xml and registration to the registerBeanDefinitions method of BeanDefinition Document Reader.
4: Record the number of loads and return.
- Continue with the registerBeanDefinitions method of BeanDefinitionDocumentReader:
/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */ @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { //Instantiate ReaderContext this.readerContext = readerContext; //register doRegisterBeanDefinitions(doc.getDocumentElement()); }
- RegiserBean Definitions doesn't do anything, so let's continue to look at the doRegisterBean Definitions method:
/** * Register each bean definition within the given root {@code <beans/>} element. */ @SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...) protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); //Verify XML namespace, BeanDefinition ParserDelegate. BEANS_NAMESPACE_URI if (this.delegate.isDefaultNamespace(root)) { //Get Attribute String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } //Parsing Preprocessing, Content null Leave a Subclass Implementation preProcessXml(root); //analysis parseBeanDefinitions(root, this.delegate); //Parsing post-processing, content null left a subclass implementation postProcessXml(root); this.delegate = parent; }
The most important way to validate xml namespace in doRegisterBean Definitions is parseBean Definitions, parseBean Definitions method is parsing operation.
- The source code of the parseBeanDefinitions method:
/** * 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) { //Verify XML namespace, BeanDefinition ParserDelegate. BEANS_NAMESPACE_URI 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)) { //Default Label Processing parseDefaultElement(ele, delegate); } else { //Processing of custom tags delegate.parseCustomElement(ele); } } } } else { //Processing of custom tags delegate.parseCustomElement(root); } }
In the parseBean Definitions method, tags have been parsed to distinguish between default tags and custom tags. This time, we only parse the source code of default tags and customize tags to DeBug.
- The source code of the parseDefaultElement method:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { //Parsing import Tags if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } //Resolve alias tags and register else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } //Resolve bean tags and register else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } //Parsing beans Tags else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
Here we can see that spring's parsing process for import/bean/alias/beans can not be the parsing of bean labels in beans. Spring directly calls the doRegisterBean Definitions method again. Next, we will analyze the bean labels.
- ProceBeanDefinition method:
/** * Process the given bean element, parsing the bean definition * and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { //Delegate the parseBeanDefinitionElement method of BeanDefinitionParserDelegate for element parsing and returning //BeanDefinitionHolder instance, BeanDefinitionHolder already contains various attributes in the configuration file BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); //When BeanDefinition Holder returns null-free, subtags with weak default tags have custom attributes and need to be parsed again. if (bdHolder != null) { //Resolve custom tags in default tags bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. // For instance registration, the BeanDefinition ReaderUtisl. RegiserBeanDefinition handles it. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
In the processBeanDefinition method, spring does two things:
1: Delegate the parseBeanDefinitionElement method of BeanDefinitionParserDelegate for element parsing and return to the BeanDefinitionHolder instance, which already contains various attributes in the configuration file.
2: bean Definition Holder obtained above is used to register beans. bean Definition ReaderUtils. RegiserBean Definition method is used.
- xml parsing is done by delegate.parseBeanDefinitionElement method:
/** * Parses the supplied {@code <bean>} element. May return {@code null} * if there were errors during parse. Errors are reported to the * {@link org.springframework.beans.factory.parsing.ProblemReporter}. */ @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { //Parsing id attributes String id = ele.getAttribute(ID_ATTRIBUTE); //Resolve the name attribute String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); //Split name attribute 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; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isTraceEnabled()) { logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } //Encapsulating information into bean Definition AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { //beanname is created using default rules if it does not exist 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.isTraceEnabled()) { logger.trace("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); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
Three things are done in the parseBeanDefinitionElement method:
1: parse id/name;
2: Check the uniqueness of name;
3: Encapsulate the information into the bean Definition, and then look directly at the parseBean Definition Element method.
- ParseBean Definition Element source code:
/** * 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) { this.parseState.push(new BeanEntry(beanName)); String className = null; //Resolving classname attributes if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } String parent = null; //Parsing parent attributes if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { //Creating an AbstractBeanDefinition type for carrying properties AbstractBeanDefinition bd = createBeanDefinition(className, parent); //Resolve various properties of bean s parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); //Extract description bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); //Parsing meta (metadata) parseMetaElements(ele, bd); //Analyzing 53 pages of Lookup-method book has usage methods parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); //Analyzing 55 pages of replaced-method book has usage methods parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); //Constructor parameters //Analyzing the back of replaced-method in constructor-arg book parseConstructorArgElements(ele, bd); //Analyzing Behind Replace-Method in Property Book parsePropertyElements(ele, bd); //Analyzing Behind Property in Qualifier 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; }
From the above code, we can see that the first step is to instantiate an AbstractBean Definition to carry various xml attributes, then parse the various values of your attributes in xml through the parseBean Definition Attributes method, and then parse lookUp-method (method injection), replaced-method (replacement method or method return value). Constructor parameter constructor-arg, property attribute, Qualifier attribute and so on; the source code of the above methods is not shown one by one, but are all parsed through Element;
- Next, look at the code BeanDefinition ReaderUtils. RegiserBeanDefinition, which is actually registered.
@Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { //beanName cannot be empty Assert.hasText(beanName, "Bean name must not be empty"); //Bean Definition cannot be empty Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { //Check Method Overrides, which was mentioned when parsing and assembling bean Definition ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } //beanDefinitionMap stores global Maps of instances using Concurrent HashMap thread security BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); //If you have registered to process content if (existingDefinition != null) { //Coverage or not if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } else if (existingDefinition.getRole() < beanDefinition.getRole()) { // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE if (logger.isInfoEnabled()) { logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (!beanDefinition.equals(existingDefinition)) { if (logger.isDebugEnabled()) { logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else { if (logger.isTraceEnabled()) { logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } this.beanDefinitionMap.put(beanName, beanDefinition); } else { //Determine whether AbstractBeanFactory.alreadyCreated has been created at least once to determine 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 the initial registration stage // Register beanDefinitionMap new instance beanName + beanDefinition this.beanDefinitionMap.put(beanName, beanDefinition); // Add bean Definition Names this.beanDefinitionNames.add(beanName); // Clear Cache this.manualSingletonNames.remove(beanName); } // Clear Cache this.frozenBeanDefinitionNames = null; } if (existingDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); } }
The above code first verifies that the beanName and BeannDefinition are not empty, and then continues to verify that the method Overrides Method Overrides mentioned in the source code of lookup-method and recpse-method when parsing and assembling the bean Definition, and continues to determine whether the bean Definition Map exists, if the bean already exists, through allowBean Definition Map. The Definition Overridding attribute determines whether it can be overridden or not, and throws an exception; if it does not exist, it needs to determine whether this is the first registered bean; if it does, it initializes the bean Definition Map and then carries on the put operation; otherwise, it directly puts the bean Definition Map to complete the registration;
So far, we have finished the xml parsing and registration source code part of the whole XmlBeanFactory. I believe that this article can not really understand, but also need the reader to download the source code to run with debug, combined with the comments of this article, I believe it will be easy to understand, the codeword is not easy to forward, please indicate the source: https://blog.csdn.net/qq_3025...
Blog address: https://lantaogithub.github.io
Blog address: https://lantaogithub.github.io
Brief Book: https://www.jianshu.com/u/bfb...
CSDN: https://blog.csdn.net/qq_3025...
Open Source China: https://my.oschina.net/u/3948555
Nuggets: https://juejin.im/user/5c8c6f...