Antecedents to review

Last time, “Spring IoC Container Initialization (2),” we talked about how Spring parsed the

tag we defined. The code went through layer after layer, Finally came to the BeanDefinitionParserDelegate# parseBeanDefinitionElement method. However, this method is superficial and does not delve into parsing properties like class and child tags like property in

.

This article continues.

Well, be patient and write your own demo to follow the interruption points, so that you can understand more deeply.

How do you parse the contents of <bean>?

Moving on to the code:

public class BeanDefinitionParserDelegate {
    public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, @Nullable BeanDefinition containingBean) {

        String className = null;
        // Read the class attribute of the 
      
        tag
      
        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        }
        String parent = null;
        // Read the parent property of the 
      
        tag
      
        if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
            parent = ele.getAttribute(PARENT_ATTRIBUTE);
        }

        try {
            // GenericBeanDefinition object
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);

            // Parse scope, lazy-init, autowire, etc
            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
	    bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            // Parse meta tags
            parseMetaElements(ele, bd);
            
            // parse the lookup-method tag
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());

            // Parse the replace-method tag
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

            // Parse the constructor-arg tag
            parseConstructorArgElements(ele, bd);

            // Parse the property tag
            parsePropertyElements(ele, bd);
            
            // Resolve the Qualifier tag
            parseQualifierElements(ele, bd);

            bd.setResource(this.readerContext.getResource());
            bd.setSource(extractSource(ele));

            return bd;
        }
        // catch ...

        return null; }}Copy the code

This is where you actually parse the contents of <bean> tags, such as the usual class, parent, scope, lazy-init, Autowire, property, constructor-arg, etc. There are also unusual lookup-method, replace-method, and so on.

The method internally calls one method after another to parse different tags. Here we only follow up the common property analysis, other methods are generally similar, you can study by yourself if you are interested.

The parsePropertyElements method code is as follows:

public class BeanDefinitionParserDelegate {
    // Parse the property tag
    public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
        NodeList nl = beanEle.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            // Filter the 
      
        tag
      
            if(isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { parsePropertyElement((Element) node, bd); }}}public void parsePropertyElement(Element ele, BeanDefinition bd) {
        // property Specifies the name attribute of the label
        String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
        if(! StringUtils.hasLength(propertyName)) {// error
            return;
        }
        this.parseState.push(new PropertyEntry(propertyName));
        try {
            if (bd.getPropertyValues().contains(propertyName)) {
                // error
                return;
            }
            // This resolves to a RuntimeBeanReference or TypedStringValue
            Object val = parsePropertyValue(ele, bd, propertyName);
            PropertyValue pv = new PropertyValue(propertyName, val);
            parseMetaElements(ele, pv);
            pv.setSource(extractSource(ele));
            // Add the parsed value to BeanDefinition's property list
            bd.getPropertyValues().addPropertyValue(pv);
        }
        finally {
            this.parseState.pop(); }}}Copy the code

What does this method basically do?

  1. Walk through the node and find the property label
  2. Parse the name attribute of the property tag and encapsulate its corresponding value as either RuntimeBeanReference or TypedStringValue (where the former corresponds to the ref attribute and the latter to the value attribute, See the application-IOC.xml file above), then encapsulate it as PropertyValue and save it to BeanDefinition’s property list.

The process for resolving ref and value is as follows:

public class BeanDefinitionParserDelegate {

    public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) { String elementName = (propertyName ! =null ?
                "<property> element for property '" + propertyName + "'" :
                "<constructor-arg> element");

        // Should only have one child element: ref, value, list, etc.
        NodeList nl = ele.getChildNodes();
        Element subElement = null;
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceofElement && ! nodeNameEquals(node, DESCRIPTION_ELEMENT) && ! nodeNameEquals(node, META_ELEMENT)) {// Child element is what we're looking for.
                if(subElement ! =null) {
                    error(elementName + " must not contain more than one sub-element", ele);
                }
                else{ subElement = (Element) node; }}}// The ref and value attributes cannot coexist
        boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
        boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
        if((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement ! =null)) {
            error(elementName +
                   " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
        }

        / / ref attribute
        if (hasRefAttribute) {
            String refName = ele.getAttribute(REF_ATTRIBUTE);
            if(! StringUtils.hasText(refName)) { error(elementName +" contains empty 'ref' attribute", ele);
            }
            // Encapsulate as RuntimeBeanReference
            RuntimeBeanReference ref = new RuntimeBeanReference(refName);
            ref.setSource(extractSource(ele));
            return ref;
        }
        / / the value attribute
        else if (hasValueAttribute) {
            // Encapsulate as TypedStringValue
            TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
            valueHolder.setSource(extractSource(ele));
            return valueHolder;
        }
        // If there are child elements, continue parsing
        else if(subElement ! =null) {
            // This contains the child tags of the property tag, such as list, map, set, etc
            return parsePropertySubElement(subElement, bd);
        }
        else {
            error(elementName + " must specify a ref or value", ele);
            return null; }}}Copy the code

Property tags are relatively complex to parse, and other tags (meta, constructor-arg, etc.) are generally similar.

After BeanDefinitionParserDelegate# parseBeanDefinitionElement method after parsing and encapsulation, have saved our custom BeanDefinition bean information, GenericBeanDefinition namely. Spring also wraps BeanDefinitionHolder and alias information as BeanDefinitionHolder:

public class BeanDefinitionParserDelegate {
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        // ...

        // Parse the BeanDefinition
        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        if(beanDefinition ! =null) {
            // ...
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null; }}Copy the code

In addition, in registered before with the IoC container, there is also a decorateBeanDefinitionIfRequired method, it is mainly used to deal with a default namespace (www.springframework.org/schema/bean.) Other bean definitions, such as

,

, etc., will continue to follow the main line here without further analysis.

The next step is to register the BeanDefinition with the IoC container:

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
    // ...
    
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        // BeanDefinitionHolder encapsulated as BeanDefinitionHolder
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if(bdHolder ! =null) {
            // Process beans that are not in the default namespace
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // Register the final decorated instance.
                / / register BeanDefinition
                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(newBeanComponentDefinition(bdHolder)); }}}Copy the code
public abstract class BeanDefinitionReaderUtils {
    // ...

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

        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if(aliases ! =null) {
            for(String alias : aliases) { registry.registerAlias(beanName, alias); }}}}Copy the code

What is the IoC container? How do I register?

Above mentioned, the Spring IoC container by default is DefaultListableBeanFactory, look at its inheritance structure:

You can see DefaultListableBeanFactory BeanDefinitionRegistry interface is realized.

The so-called “registered” to the IoC container, is actually storing BeanDefinition DefaultListableBeanFactory holding a Map, as follows:

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory.BeanDefinitionRegistry.Serializable {
    // ...

    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 {
                ((AbstractBeanDefinition) beanDefinition).validate();
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex); }}// Get an existing BeanDefinition
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if(existingDefinition ! =null) {
            if(! isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            }
            // Do these exceptions look familiar?
            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 {
            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; removeManualSingletonName(beanName); }}else {
                // Register to Map
                // Still in startup registration phase
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                removeManualSingletonName(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if(existingDefinition ! =null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        }
        else if(isConfigurationFrozen()) { clearByTypeCache(); }}}Copy the code

Do the above anomalies look familiar?

What is this beanDefinitionMap? It is a Map:

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
Copy the code

summary

At this point, Spring has read and parsed the <bean> tag information from the application-IOC. XML file we defined and converted it into an internal data structure, BeanDefinition, Then register to the IoC container (i.e. DefaultListableBeanFactory).

In order to have an overall grasp, the main process is organized into a mind map:

In fact, the previous several articles are mainly the first step, that is, “initialize BeanFactory, register Bean definition”, and only follow a main line down, other details interested partners can study by themselves.

The IoC container has been created, and the BeanDefinition has been placed init. How do we get the objects we want from the container?

To find out what happens next, listen next time