Spring source code analysis 2 Parse the Xml configuration file and register the BeanDefinition to the container

Posted by ranjuvs on Sun, 12 Dec 2021 13:25:29 +0100

1. Important core interfaces

1.1 resource interface

Inheritance system

The Resource interface represents a Resource class, which is essentially an InputStream. It is used to read a configuration file and encapsulate it into a Resource.

1.2BeanDefinition interface

BeanDefinition inheritance system

According to the English meaning, bean definition includes all attributes of a bean, of which the core is the id, class, scope, isLazy, init method, factory method, etc. of the bean we configured.

1.3BeanDefinitionRegistry interface

BeanFactory interface implements this interface. Generally, BeanDefinitionRegistry implementation class is the implementation class object of BeanFactory, that is, IOC container.

After the XmlBeanDefinitionReader parses the Resource as BeanDefinition(s) below, these BeanDefinition information will be stored in the Map of BeanFactory(IOC container). Finally, the object is created according to the BeanDefinition and placed in the container.

1.4BeanDefinitionReader interface

It is used to parse resources encapsulated by resources and read configuration files of different types. Resolve the Resource to BeanDefinition and register it in IOC.

2. Parsing and loading bean of xmlbeanfactory initialization

2.1. Test code

public class BeanFactoryTest {
	public static void main(String[] args) {
        //Create a BeanFactory object through the Spring configuration file
		BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-bf.xml"));
        //Gets the object in the container
		Object a = beanFactory.getBean("componentA");
		Object b = beanFactory.getBean("componentB");
		System.out.println(a);
		System.out.println(b);
	}
}

2.2 flow chart

Detailed flow chart

Overall flow chart

3. Source code analysis

3.1 core methods of xmlbeandefinitionreader class

3.1.1loadBeanDefinitions()

		public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Loading XML bean definitions from " + encodedResource);
		}
		//Get all encdoeresources loaded by the current thread from ThreadLocal
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

		//Add the current resource to threadLocal. The failure indicates that the current coderresource has been loaded and cannot be loaded repeatedly
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		//Gets the input stream object wrapped by encodedResource
		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
			//Because the sax parser is used to parse the xml next, the input stream needs to be wrapped as inputSource, which is the object representing the resource in sax
			InputSource inputSource = new InputSource(inputStream);
			//Set encoding
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			//Parse the xml and load the entry of beanDefinition
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			//After loading, remove the current resource from the resource set
			currentResources.remove(encodedResource);
			/*
			 * The value (current currentResources) of the Entry with the current threadLocal as the key in the threadlocalMap inside the current thread is NULL.
			 * The remove() method is called here to kill the entry. Prevent memory leaks
			 */
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

3.1.2doLoadBeanDefinitions()

	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			//Convert the resource into a hierarchical document object that can be recognized at the program level.
			Document doc = doLoadDocument(inputSource, resource);
            
			/*
             * Parse the document into beanDefinition and register it in beanFactory, and finally return the number of beandefinitions newly registered * in beanFactory
			 */
			 int count = registerBeanDefinitions(doc, resource);
            
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			//Returns the number of newly registered bd.
			return count;
		}
	}

3.1.3registerBeanDefinitions()

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		//Create a beanDefinitionDocumentReader. For each document object, a beanDefinitionDocumentReader object will be created.
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

		/*
	     * getRegistry() The beanFactory instance created by the program is returned.			
         * countBefore: The number of bd in the container before parsing the doc.
         * 
	     * (This method is in the parent class AbstractBeanDefinitionReader of XmlBeanDefinitionReader 		 *  A method returns its internal BeanDefinitionRegistry implementation class, which we analyzed in the first section 		 *  The implementation class is the BeanFactory implementation class, i.e. IOC container)
         */
         
		int countBefore = getRegistry().getBeanDefinitionCount();

		/*Resolve the doc and register it in beanFactory.
		 * xmlReaderContext: The most important parameter is the current this - > 					                *  XmlBeanDefinitionReader contains BeanFactory.
		 */
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

		//The number of newly registered beandefinitions returned.
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

3.2DefaultBeanDefinitionDocumentReader

3.2.1registerBeanDefinitions()

	@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
        //Pass in top-level tags < beans > < / beans >
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}

3.2.2doRegisterBeanDefinitions()

protected void doRegisterBeanDefinitions(Element root) {
		BeanDefinitionParserDelegate parent = this.delegate;
    	
    	//Returns a beans tag parser object
		this.delegate = createDelegate(getReaderContext(), root, parent);
		
    	//Generally, it will be established
		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
		
//If the condition is true, it means that the current environment does not support these tags to return directly;                
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;
				}
			}
		}
    	
    	//Left to subclass implementation
		preProcessXml(root);
    
    	//Parsing incoming beans tags and parsers
		parseBeanDefinitions(root, this.delegate);
        //Left to subclass implementation
		postProcessXml(root);
		this.delegate = parent;
	}

3.2.3parseBeanDefinitions

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
            
            /*
             * Gets the child tag inside the beans tag
             */
			NodeList nl = root.getChildNodes();
            //Traverse all sub tags for parsing
			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)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
        //Resolve custom labels
		else {
			delegate.parseCustomElement(root);
		}
	}

3.2.4parseDefaultElement()

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		//The condition holds: it indicates that the ele tag is an import tag.
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		//If the condition is true, it indicates that the ele tag is an alias tag.
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		//The condition holds: it indicates that the ele tag is a bean tag
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		//The condition holds: it indicates that the ele tag is a nested beans tag
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}

3.2.5processBeanDefinition()

	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		//After parsing, return to bdHolder, which mainly saves the alias information on the bean tag.
        //Contains the BeanDefinition attribute internally.
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

		if (bdHolder != null) {
			//If beanDefinition needs decoration, it will be processed, mainly dealing with custom attributes.
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

			try {

				//Register bd into the container.
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}

			//Send a bd registration completion event.
			// Send registration event.
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

3.3BeanDefinitionParserDelegate

3.3.1*parseBeanDefinitionElement()

	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        //Get the id attribute of the bean tag (the id and name of the bean tag are not required attributes, but the class attribute is)
		String id = ele.getAttribute(ID_ATTRIBUTE);
        //Get the name attribute of the bean tag (multiple name attributes can be set, separated by commas)
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
		//Construct a collection and put all aliases in the collection
		List<String> aliases = new ArrayList<>();
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            //Set all aliases after splitting to the collection
			aliases.addAll(Arrays.asList(nameArr));
		}
        
		//By default, the id attribute is assigned to beanName.
		String beanName = id;
        
        /*
         * Here is the judgment. If the id attribute is NULL but the alias is set, the first alias in the alias list will be set
         * Set to beanName and remove this alias from the alias collection.
         */
		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);
		}
		
        /*
         *  The core parses and encapsulates the bean tag as returned by BeanDefinition according to beanName and bean tag
		 */
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        
        //Encapsulated beanDefinition object is not NULL.
		if (beanDefinition != null) {
            /* 
             * beanName NULL indicates that the bean tag is not set with id and name. The following is the automatic generation for it 			  *  The logic of beanName
             * The beanName generated by default is the full class name + # + sequence number
             */
			if (!StringUtils.hasText(beanName)) {
				try {
					if (containingBean != null) {
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
                        //The tool class generates a beanName
						beanName = this.readerContext.generateBeanName(beanDefinition);
						//Get full class name 
						String beanClassName = beanDefinition.getBeanClassName();
                        
                        /*
                         * If the current bean classname is not used as a bean name,
                         * Then use the bean classname as the alias of the bean.
                         */                      
						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);
            /*
             * Wrap the created beanDefinition object into beanDefinition and pass in beanName and
             * Alias list
             */
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}
		return null;
	}

3.3.2*parseBeanDefinitionElement()

	@Nullable
	public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, @Nullable BeanDefinition containingBean) {
		
        //Set parser status
		this.parseState.push(new BeanEntry(beanName));
		
        //Gets the class attribute on the bean tag
		String className = null;
		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		}
        
        //Gets the parent attribute on the bean tag
		String parent = null;
		if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
			parent = ele.getAttribute(PARENT_ATTRIBUTE);
		}

		try {
            
           	//A bd object is created. The bd object only sets the class information and parent class name attributes.
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);

			//Parse the attribute information defined on the bean tag: lazy init, init method, dependencies on
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);

			//<bean>
			// <description>xxxxxx</description>
			// </bean>
			//Read the information of the description sub tag and save it in bd.
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

			//analysis
			// <bean>
			//    <meta key="meta_1" value="meta_val_1"/>
			//    <meta key="meta_2" value="meta_val_2"/>
			// </bean>
			parseMetaElements(ele, bd);

			//Resolve the lookup method sub tag, and the bd.methodOverrides property saves the method that needs to be overwritten. Dynamic proxy implementation.
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());

			//Resolve replace method sub tag
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

			//Parsing construction method parameter sub Tags
			parseConstructorArgElements(ele, bd);
			//Resolve attribute sub Tags
			parsePropertyElements(ele, bd);
			//Resolve qualifier child Tags
			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 c lass [" + 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;
	}

3.3.3*createBeanDefinition()

	public static AbstractBeanDefinition createBeanDefinition(
			@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
		
       /*
        * A BeanDefinition object is really created here, because BeanDefinition is an interface, which is constructed here 		  *	 Is its implementation class GenericBeanDefinition() object.
        */
		GenericBeanDefinition bd = new GenericBeanDefinition();
        //Set the name of the parent class
		bd.setParentName(parentName);
		if (className != null) {
			if (classLoader != null) {
                //Get the Class object according to the full Class name and assign it to the beanClass attribute (Object) inside bd
				bd.setBeanClass(ClassUtils.forName(className, classLoader));
			}
			else {
				bd.setBeanClassName(className);
			}
		}
        //Returns the created beanDefinition object
		return bd;
	}

3.3.4*parseBeanDefinitionAttributes()

Resolve various properties on the bean tag and set them in the beanDefinition object.

	public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
			@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {

		if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
			error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
		}
        //scope property
		else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
			bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
		}
		
		//lazyInit property
		String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
		if (isDefaultValue(lazyInit)) {
			lazyInit = this.defaults.getLazyInit();
		}
		bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
		
		String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
		bd.setAutowireMode(getAutowireMode(autowire));
		
        //Depdnes on attribute
		if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
			String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
			bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
		}

		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));
		}

		if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
			bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
		}
		
        //init - method attribute, which defines a method in the class and will be called during the bean declaration cycle
		if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
			String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
			bd.setInitMethodName(initMethodName);
		}
		
        //Destruction method attribute, similar to init method, is called when the factory is closed
		if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
			String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
			bd.setDestroyMethodName(destroyMethodName);
		}

		//Factory bean property, used to create complex objects
		if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
			bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
		}
		if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
			bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
		}
		return bd;
	}

3.4 final registration in container

	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
        
        /*
         * Here, the key value mapping is finally performed according to beanName and BeanDefinition,
         * Stored in beanDefinitionMap inside BeanFactory (it is a ConcurrentHashMap type)
         *				||
         * (beanDefinition Map.put(beanName, beanDefinition))
         */
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		/*
		 * The alias registration is to register in a map < string, string > (also the ConcurrentHashMap class) 		 *	 Type), key is the alias, and value is the beanName. A redirection will be performed when getBean("alias").
		 */
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

4. Summary

In the parsing phase, the BeanDefinitionReader interface provided by Spring is used to parse different configurations (for example, xml Configuration files are parsed using XmlBeanDefinitionReader class or Configuration class (@ Configuration annotated class)). Finally, the bean tag is parsed as BeanDefinition object and registered in the container (finally put into beanDefinitionMap, and the key is beanname (generally id) , value is the BeanDefinition object). The way to handle the alias is to put (alias - > beanname) into a Map, and then redirect it when getBean().

  • When the bean tag does not set the id attribute and name attribute, an id (full class name + # + sequence number) will be generated by default, and if the full class name has not been used in the container, it will be set as the alias of the bean.
  • When the id is not set but the name attribute is set, the first alias of the name attribute will be regarded as the id, and the first alias of the name attribute will not be used as the alias (the source code has removed()).

Topics: Spring xml Container source code ioc