Popular understanding of spring source code -- parsing and registering bean definitions

Popular understanding of spring source code (5) -- parsing and registering bean definitions

Last section talked about how to get document. After converting the document to document, the next extraction and registration bean is our play.

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

        try {
            //Convert from resource file to document object
            Document doc = doLoadDocument(inputSource, resource);
            //analysis document,And register beanDefiniton To the factory
            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);
        }
    }

In the doLoadBeanDefinitions method of xmlBeanDefinitionReader, give the document object to the registerBeanDefinitions method, and return the number of beandefinitions loaded this time.

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //Create default documentReader,Namely DefaultBeanDefinitionDocumentReader,Used to parse document
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        //Get the registration center and record the BeanDefinition Number of loads
        int countBefore = getRegistry().getBeanDefinitionCount();
        //Load and register BeanDefinition
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        //Record the loaded BeanDefinition Number
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

The first step is to create a readercontext (resource). The code is very simple.

    public XmlReaderContext createReaderContext(Resource resource) {
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                this.sourceExtractor, this, getNamespaceHandlerResolver());
    }

The key point here is to put this object, that is, the current xmlBeanDefinitionReader object into it. Therefore, XmlReaderContext is equivalent to a context, which is convenient for data transmission, similar to ServletContext, SecurityContext, etc.

Then give the document and context objects to the documentReader.registerBeanDefinitions method.

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        doRegisterBeanDefinitions(doc.getDocumentElement());
    }

The previous context is quoted, and then called doRegisterBeanDefinitions. In spring, many of the method names starting with "do" are truly working methods.

Here we get the root element of the document and enter the documentReader.doRegisterBeanDefinitions method

    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;
        //Entrusted to delegate analysis
        this.delegate = createDelegate(getReaderContext(), root, parent);

        //Judge current Beans Is node the default namespace
        if (this.delegate.isDefaultNamespace(root)) {
            //Obtain beans Node's profile attribute
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                //You can use commas or semicolons to beans Label specified as multiple profile type
                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.
                //Judge current beans Tagged profile Is it activated
                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, left to subclass implementation
        preProcessXml(root);
        //Real parsing process
        parseBeanDefinitions(root, this.delegate);
        //After parsing, leave it to subclass implementation
        postProcessXml(root);

        this.delegate = parent;

You can first see what this method is doing according to my comments. Here, the documentReader object references a BeanDefinitionParserDelegate, and delegates the parsing process to the delegate processing, which is implemented in the parseBeanDefinitions(root, this.delegate) method.

At the beginning of the method, there is a large English comment, which roughly means that any embedded beans tag will lead to the recursive call of the method. In order to correctly propagate and retain the default attribute of the < beans > tag and track the current delegate (i.e. the parent delegate, which may be null), a new delegate (i.e. the child delegate) will be created each time, referencing the parent delegate, and The child delegate will be the parent next time.

Is the melon seed a little buzzing??? But it can also be understood that this is to deal with the default attribute of the beans tag.

Because when we configure xml files, we can nest beans tags in the root beans tags (although there are few such writing methods, usually written in another xml file, and then imported through the import tag, but the effect is the same in fact), similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byType">
    <beans profile="test" default-autowire="byType">
        <bean id="user" class="cn.zxh.po.User" >
            <property name="name" value="Zhang San"/>
        </bean>
    </beans>
    <beans profile="dev" default-autowire="constructor">
        <bean id="user" class="cn.zxh.po.User">
            <property name="name" value="Li Si"/>
        </bean>
    </beans>
</beans>

In theory, when there is no stack overflow, beans can be nested infinitely inside (not necessarily correct, not tried). Later, it will be mentioned that each time the beans label is parsed, it will enter the method, so the method may be called recursively, and a delegate will be created each time, corresponding to a beans label. The default attribute of the current delegate will be determined according to the parent delegate Sex.

1. Create Delegate

        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

Create delete Click to enter.

    protected BeanDefinitionParserDelegate createDelegate(
            XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {

        BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
        //According to the father delegate Initializes the current beans Default properties for
        delegate.initDefaults(root, parentDelegate);
        return delegate;
    }

First, a BeanDefinitionParserDelegate object is instantiated, which references the previous context, readerContext, and also a DocumentDefaultsDefinition.

    private final DocumentDefaultsDefinition defaults = new DocumentDefaultsDefinition();
    public BeanDefinitionParserDelegate(XmlReaderContext readerContext) {
        Assert.notNull(readerContext, "XmlReaderContext must not be null");
        this.readerContext = readerContext;
    }

DocumentDefaultsDefinition saves the default configuration attribute values of the root node, such as default lazyinit, default autowire, default initmethod, default destroymethod, etc. these attributes should all be used. If the beans under the beans tag are not configured with these attributes, the default configuration of the beans tag will be used.

So here, delegate refers to a DocumentDefaultsDefinition, which will work when it parse each bean tag in the future, and then call initDefaults(root, parentDelegate), which is to initialize its own DefaultsDefinition based on the parent delegate.

In the initDefaults method, we probably use the principle of sub configuration priority to assign values to the DocumentDefaultsDefinition property. We won't take a closer look at it. Otherwise, it's easy to get started and give up. After several layers of method calls, we finally enter this method. Here's just the code.

    protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable DocumentDefaultsDefinition parentDefaults, Element root) {
        String lazyInit = root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE);
        if (isDefaultValue(lazyInit)) {
            // Potentially inherited from outer <beans> sections, otherwise falling back to false.
            lazyInit = (parentDefaults != null ? parentDefaults.getLazyInit() : FALSE_VALUE);
        }
        defaults.setLazyInit(lazyInit);

        String merge = root.getAttribute(DEFAULT_MERGE_ATTRIBUTE);
        if (isDefaultValue(merge)) {
            // Potentially inherited from outer <beans> sections, otherwise falling back to false.
            merge = (parentDefaults != null ? parentDefaults.getMerge() : FALSE_VALUE);
        }
        defaults.setMerge(merge);

        String autowire = root.getAttribute(DEFAULT_AUTOWIRE_ATTRIBUTE);
        if (isDefaultValue(autowire)) {
            // Potentially inherited from outer <beans> sections, otherwise falling back to 'no'.
            autowire = (parentDefaults != null ? parentDefaults.getAutowire() : AUTOWIRE_NO_VALUE);
        }
        defaults.setAutowire(autowire);

        if (root.hasAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)) {
            defaults.setAutowireCandidates(root.getAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE));
        }
        else if (parentDefaults != null) {
            defaults.setAutowireCandidates(parentDefaults.getAutowireCandidates());
        }

        if (root.hasAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)) {
            defaults.setInitMethod(root.getAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE));
        }
        else if (parentDefaults != null) {
            defaults.setInitMethod(parentDefaults.getInitMethod());
        }

        if (root.hasAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)) {
            defaults.setDestroyMethod(root.getAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE));
        }
        else if (parentDefaults != null) {
            defaults.setDestroyMethod(parentDefaults.getDestroyMethod());
        }

        defaults.setSource(this.readerContext.extractSource(root));
    }

2. Determine whether the current profile is activated

We know that spring supports the configuration of multiple profiles, which can be configured in the profile attribute of the beans tab, and then the spring.active.profile can be specified dynamically before running. In the java project, you can configure the system property System.setProperty("spring.profiles.active","test"), configure the ServletContext context parameter in the web project, and specify it in the springboot through spring.active.profile.

        //Judge current Beans Is node the default namespace
        if (this.delegate.isDefaultNamespace(root)) {
            //Obtain beans Node's profile attribute
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                //You can use commas or semicolons to beans Label specified as multiple profile type
                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.
                //Judge current beans Tagged profile Is it activated
                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;
                }
            }
        }

It's much easier to know how to use it. First, get the specified profile array of beans tag, and compare it with the specified spring.active.profile. If it meets the conditions, the beans will be loaded.

First, get the StandardEnvironment through getReaderContext().getEnvironment(). Here, getReaderContext() is to get the XmlReaderContext context just mentioned, and then get the standard environment referenced by XmlBeanDefinitionReader during initialization from the context.

The following is the construction method that XmlBeanDefinitionReader inherits from the abstract parent class.

    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;

        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        }
        else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        }
        else {
            this.environment = new StandardEnvironment();
        }
    }

The standard environment initialization here will load the current system and environment variables, which is the encapsulation of system and environment variables. The customizePropertySources method is called in the constructor inherited from the parent class by StandardEnvironment

private final MutablePropertySources propertySources = new MutablePropertySources()
protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(
                new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(
                new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

This method saves the current system and environment variables in their propertySources property.

public class MutablePropertySources implements PropertySources {

    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
    /**
     * Create a new {@link MutablePropertySources} object.
     */
    public MutablePropertySources() {
    }
}

MutablePropertySources has a List property that holds multiple property sources.

That is to say, when the initialization of StandardEnvironment is completed, the system variables and environment variables will be loaded, and then the acceptsProfiles (specifiedProfiles) method will be called here to determine whether the profile of the current beans label should be loaded, traverse the given profiles array, and return true as long as one is specified as spring.active.profile.

    public boolean acceptsProfiles(String... profiles) {
        Assert.notEmpty(profiles, "Must specify at least one profile");
        for (String profile : profiles) {
            if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
                if (!isProfileActive(profile.substring(1))) {
                    return true;
                }
            }
            else if (isProfileActive(profile)) {
                return true;
            }
        }
        return false;
    }
    protected boolean isProfileActive(String profile) {
        validateProfile(profile);
        Set<String> currentActiveProfiles = doGetActiveProfiles();
        return (currentActiveProfiles.contains(profile) ||
                (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
    }

The key point is to get the method of spring.active.profile.

    protected Set<String> doGetActiveProfiles() {
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) {
                //from propertySources Obtained from, key by spring.active.profile
                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }

3. Parse the root node parseBeanDefinitions

After the creation of the delegate class, spring.active.profile judges the beans tag that returns true, and then enters parsebean definitions for parsing

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //Determine whether it is the default label
        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)) {
                        //Resolution of default label
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        //Analysis of custom label
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            //Analysis of custom label
            delegate.parseCustomElement(root);
        }
    }

It should be clear at a glance to see whether the root node and its child nodes are the default tags. The default tags and the custom tags have different parsing methods. In addition to the beans, beans, alias and import tags, they are all custom tags. The custom tags need to implement some interfaces and configurations. If it is the default label, enter the parseDefaultElement method.

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        //analysis import Label
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        //analysis alias Label
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        //analysis bean Label
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        //analysis beans Label
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

In the next chapter of the specific parsing process, we will talk about that. Here, when we look at parsing beans tags, will we call the doRegisterBeanDefinitions method again? Remember? This is exactly the explanation that this method will call recursively before. Paste the code again.

    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;
        //Entrusted to delegate analysis
        this.delegate = createDelegate(getReaderContext(), root, parent);

        //Judge current Beans Is node the default namespace
        if (this.delegate.isDefaultNamespace(root)) {
            //Obtain beans Node's profile attribute
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                //You can use commas or semicolons to beans Label specified as multiple profile type
                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.
                //Judge current beans Tagged profile Is it activated
                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, left to subclass implementation
        preProcessXml(root);
        //Real parsing process
        parseBeanDefinitions(root, this.delegate);
        //After parsing, leave it to subclass implementation
        postProcessXml(root);

        this.delegate = parent;
    }

 

Go too far, don't forget why!

 

Reference: deep analysis of spring source code

Keywords: Java xml Spring Attribute encoding

Added by TEENFRONT on Wed, 06 May 2020 00:53:03 +0300