Spring source code analysis - IOC default tag analysis (Part 2)

text

In the previous article, we have completed the conversion from xml configuration file to BeanDefinition. The converted instance is an instance of GenericBeanDefinition. This article mainly looks at the remaining parts of tag parsing and the registration of BeanDefinition.

Custom tag parsing in default tags

In the previous blog post, we have already analyzed the parsing of the default tag. Let’s continue with the code before watching the show. There is a method in the following picture: delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)

What is the function of this method? First, let’s take a look at this scenario, with the following configuration file:

 <bean id="demo" class="com.dabin.spring.MyTestBean">
     <property name="beanName" value="bean demo1"/>
     <meta key="demo" value="demo"/>
     <mybean:username="mybean"/>
 </bean>

There is a custom tag in this configuration file. The decorateBeanDefinitionIfRequired method is used to handle this situation. The null in it is used to pass the parent BeanDefinition. We enter its method body:

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
    
    
    return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
        Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
    
    

    BeanDefinitionHolder finalDefinition = definitionHolder;

    // Decorate based on custom attributes first.
    NamedNodeMap attributes = ele.getAttributes();
    for (int i = 0; i < attributes.getLength(); i++) {
    
    
        Node node = attributes.item(i);
        finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
    }

    // Decorate based on custom nested elements.
    NodeList children = ele.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
    
    
        Node node = children.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
    
    
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }
    }
    return finalDefinition;
}

We see that the above code has two traversal operations, one is used to traverse all attributes, and the other is to process all child nodes. Both traversal operations use decorateIfRequired(node, finalDefinition, containingBd) ;method, we continue to trace the code and enter the method body:

public BeanDefinitionHolder decorateIfRequired(
        Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
    
    
    // 获取自定义标签的命名空间
    String namespaceUri = getNamespaceURI(node);
    // 过滤掉默认命名标签
    if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
    
    
        // 获取相应的处理器
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler != null) {
    
    
            // 进行装饰处理
            BeanDefinitionHolder decorated =
                    handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
            if (decorated != null) {
    
    
                return decorated;
            }
        }
        else if (namespaceUri.startsWith("http://www.springframework.org/")) {
    
    
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
        }
        else {
    
    
            if (logger.isDebugEnabled()) {
    
    
                logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
            }
        }
    }
    return originalDef;
}

public String getNamespaceURI(Node node) {
    
    
    return node.getNamespaceURI();
}

public boolean isDefaultNamespace(@Nullable String namespaceUri) {
    
    
    //BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
    return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}

First get the namespace of the custom tag, if it is not the default namespace, get the corresponding processor based on the namespace, and finally call the processor for decorate()decoration processing. The specific decoration process will not be described here, and will be explained in detail later when analyzing custom tags.

Register the resolved BeanDefinition

For the configuration file, after the parsing and decoration are completed, the obtained beanDefinition can already meet the subsequent usage requirements. There is still registration, which is the BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry()) code in the processBeanDefinition function. Parsed. Enter the method body:

public static void registerBeanDefinition(
        BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
        throws BeanDefinitionStoreException {
    
    
    // Register bean definition under primary name.
    //使用beanName做唯一标识注册
    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);
        }
    }
}

From the above code, we see that beanName is used as the unique identifier for registration, and then all aliases are registered. The beanDefinition is eventually registered in the BeanDefinitionRegistry. Next, let's take a look at the registration process in detail.

Register BeanDefinition by beanName

In addition to using beanName as the key to put the BeanDefinition into the Map, spring also does other things. Let's take a look at the method registerBeanDefinition code. BeanDefinitionRegistry is an interface. It has three implementation classes, DefaultListableBeanFactory, SimpleBeanDefinitionRegistry, and GenericApplicationContext. SimpleBeanDefinitionRegistry is very simple. GenericApplicationContext ultimately uses the implementation method in DefaultListableBeanFactory. Let's take a look at the code:

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {
    
    

    // 校验 beanName 与 beanDefinition
    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    if (beanDefinition instanceof AbstractBeanDefinition) {
    
    
        try {
    
    
            // 校验 BeanDefinition
            // 这是注册前的最后一次校验了,主要是对属性 methodOverrides 进行校验
            ((AbstractBeanDefinition) beanDefinition).validate();
        }
        catch (BeanDefinitionValidationException ex) {
    
    
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }

    BeanDefinition oldBeanDefinition;

    // 从缓存中获取指定 beanName 的 BeanDefinition
    oldBeanDefinition = this.beanDefinitionMap.get(beanName);
    /**
     * 如果存在
     */
    if (oldBeanDefinition != null) {
    
    
        // 如果存在但是不允许覆盖,抛出异常
        if (!isAllowBeanDefinitionOverriding()) {
    
    
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                            "': There is already [" + oldBeanDefinition + "] bound.");
        }
        //
        else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
    
    
            // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
            if (this.logger.isWarnEnabled()) {
    
    
                this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                        "' with a framework-generated bean definition: replacing [" +
                        oldBeanDefinition + "] with [" + beanDefinition + "]");
            }
        }
        // 覆盖 beanDefinition 与 被覆盖的 beanDefinition 不是同类
        else if (!beanDefinition.equals(oldBeanDefinition)) {
    
    
            if (this.logger.isInfoEnabled()) {
    
    
                this.logger.info("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + oldBeanDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        else {
    
    
            if (this.logger.isDebugEnabled()) {
    
    
                this.logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + oldBeanDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }

        // 允许覆盖,直接覆盖原有的 BeanDefinition
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    /**
     * 不存在
     */
    else {
    
    
         // 检测创建 Bean 阶段是否已经开启,如果开启了则需要对 beanDefinitionMap 进行并发控制
        if (hasBeanCreationStarted()) {
    
    
            // beanDefinitionMap 为全局变量,避免并发情况
            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 {
    
    
            // 不会存在并发情况,直接设置
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            this.manualSingletonNames.remove(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }

    if (oldBeanDefinition != null || containsSingleton(beanName)) {
    
    
        // 重新设置 beanName 对应的缓存
        resetBeanDefinition(beanName);
    }
}

The processing process is as follows:

  • First, BeanDefinition is verified. This verification is also the last verification in the registration process. It mainly verifies the methodOverrides attribute of AbstractBeanDefinition.
  • Get the BeanDefinition from the cache according to the beanName. If it exists in the cache, determine whether overriding is allowed based on the allowBeanDefinitionOverriding flag. If it is allowed, override it directly, otherwise a BeanDefinitionStoreException exception will be thrown.
  • If there is no BeanDefinition with specified beanName in the cache, determine whether the current phase has started the Bean creation phase (). If so, you need to lock the beanDefinitionMap to control concurrency issues, otherwise you can set it directly. The method will hasBeanCreationStarted()be introduced in detail later, so I won’t elaborate too much here.
  • If the beanName exists in the cache or the beanName exists in the simple bean collection, call resetBeanDefinition()Reset BeanDefinition cache.

In fact, this is the core of the entire code this.beanDefinitionMap.put(beanName, beanDefinition);. The cache of BeanDefinition is not a magical thing, it just defines a ConcurrentHashMap, the key is beanName, and the value is BeanDefinition.

Register BeanDefinition by alias

Registering BeanDefinition through alias is finally implemented in SimpleBeanDefinitionRegistry. Let’s take a look at the code:

public void registerAlias(String name, String alias) {
    
    
    Assert.hasText(name, "'name' must not be empty");
    Assert.hasText(alias, "'alias' must not be empty");
    synchronized (this.aliasMap) {
    
    
        if (alias.equals(name)) {
    
    
            this.aliasMap.remove(alias);
        }
        else {
    
    
            String registeredName = this.aliasMap.get(alias);
            if (registeredName != null) {
    
    
                if (registeredName.equals(name)) {
    
    
                    // An existing alias - no need to re-register
                    return;
                }
                if (!allowAliasOverriding()) {
    
    
                    throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
                            name + "': It is already registered for name '" + registeredName + "'.");
                }
            }
            //当A->B存在时,若再次出现A->C->B时候则会抛出异常。
            checkForAliasCircle(name, alias);
            this.aliasMap.put(alias, name);
        }
    }
}

The process of the above code is summarized as follows:

(1) If alias and beanName are the same, it will be processed. If alias and beanName have the same name, there is no need to process and delete the original alias.

(2) alias coverage processing. If aliasName is already used and points to another beanName, user settings are required for processing.

(3) alias loop check, when A->B exists, if A->C->B appears again, an exception will be thrown.

Analysis of alias tag

The parsing of corresponding bean tags is the core function. The parsing of alias, import, and beans tags are all based on bean tag parsing. Next, I will analyze the parsing of alias tags. Let's go back to the parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) method and continue to look at the method body, as shown in the following figure:

When defining a bean, in addition to using id to specify the name, in order to provide multiple names, you can use the alias tag to specify. And all these names point to the same bean. In the XML configuration file, the definition of bean aliases can be completed as a separate element. We can directly use the name attribute in the bean tag, as follows:

<bean id="demo" class="com.dabin.spring.MyTestBean" name="demo1,demo2">
    <property name="beanName" value="bean demo1"/>
</bean>

There is another way to declare aliases in Spring:

<bean id="myTestBean" class="com.dabin.spring.MyTestBean"/>
<alias name="myTestBean" alias="testBean1,testBean2"/>

Let’s look specifically at the parsing process of the alias tag. The method processAliasRegistration(ele) is used for parsing. The method body is as follows:

protected void processAliasRegistration(Element ele) {
    
    
    //获取beanName
    String name = ele.getAttribute(NAME_ATTRIBUTE);
    //获取alias
    String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
    boolean valid = true;
    if (!StringUtils.hasText(name)) {
    
    
        getReaderContext().error("Name must not be empty", ele);
        valid = false;
    }
    if (!StringUtils.hasText(alias)) {
    
    
        getReaderContext().error("Alias must not be empty", ele);
        valid = false;
    }
    if (valid) {
    
    
        try {
    
    
            //注册alias
            getReaderContext().getRegistry().registerAlias(name, alias);
        }
        catch (Exception ex) {
    
    
            getReaderContext().error("Failed to register alias '" + alias +
                    "' for bean with name '" + name + "'", ele, ex);
        }
        getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
    }
}

Through the code, we can find that the parsing process is similar to the alias parsing in the bean. They both form a pair of beanName and alias alias and register it in the registry. The tracking code ultimately uses the registerAlias(String name, String alias) method in SimpleAliasRegistry

Analysis of import tag

Regarding the writing of Spring configuration files, anyone who has experienced large-scale projects knows that there are too many configuration files. The basic method used is to divide modules into modules. There are many ways to divide modules, and using import is one of them. For example, we can construct a Spring configuration file like 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">

    <bean id="demo" class="com.dabin.spring.MyTestBean" name="demo1,demo2">
        <property name="beanName" value="bean demo1"/>
    </bean>
    <import resource="lookup-method.xml"/>
    <import resource="replaced-method.xml"/>
</beans>

Use the import method to import the module configuration file in the applicationContext.xml file. If a new module is added in the future, you can simply modify this file. This greatly simplifies the complexity of post-configuration maintenance and makes the configuration modular and easy to manage. Let's take a look at how Spring parses the import configuration file. To parse the import tag, importBeanDefinitionResource(ele) is used to enter the method body:

protected void importBeanDefinitionResource(Element ele) {
    
    
    // 获取 resource 的属性值 
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    // 为空,直接退出
    if (!StringUtils.hasText(location)) {
    
    
        getReaderContext().error("Resource location must not be empty", ele);
        return;
    }

    // 解析系统属性,格式如 :"${user.dir}"
    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

    Set<Resource> actualResources = new LinkedHashSet<>(4);

    // 判断 location 是相对路径还是绝对路径
    boolean absoluteLocation = false;
    try {
    
    
        absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
    }
    catch (URISyntaxException ex) {
    
    
        // cannot convert to an URI, considering the location relative
        // unless it is the well-known Spring prefix "classpath*:"
    }

    // 绝对路径
    if (absoluteLocation) {
    
    
        try {
    
    
            // 直接根据地址加载相应的配置文件
            int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
            if (logger.isDebugEnabled()) {
    
    
                logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
            }
        }
        catch (BeanDefinitionStoreException ex) {
    
    
            getReaderContext().error(
                    "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
        }
    }
    else {
    
    
        // 相对路径则根据相应的地址计算出绝对路径地址
        try {
    
    
            int importCount;
            Resource relativeResource = getReaderContext().getResource().createRelative(location);
            if (relativeResource.exists()) {
    
    
                importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                actualResources.add(relativeResource);
            }
            else {
    
    
                String baseLocation = getReaderContext().getResource().getURL().toString();
                importCount = getReaderContext().getReader().loadBeanDefinitions(
                        StringUtils.applyRelativePath(baseLocation, location), actualResources);
            }
            if (logger.isDebugEnabled()) {
    
    
                logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
            }
        }
        catch (IOException ex) {
    
    
            getReaderContext().error("Failed to resolve current resource location", ele, ex);
        }
        catch (BeanDefinitionStoreException ex) {
    
    
            getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
                    ele, ex);
        }
    }
    // 解析成功后,进行监听器激活处理
    Resource[] actResArray = actualResources.toArray(new Resource[0]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

The process of parsing the import is relatively clear. The whole process is as follows:

  1. Get the value of the source attribute, which represents the path of the resource
  2. Parse system properties in the path, such as "${user.dir}"
  3. Determine whether the resource path location is an absolute path or a relative path
  4. If it is an absolute path, the parsing process of the Bean is called recursively and another parsing is performed.
  5. If it is a relative path, first calculate the absolute path to get the Resource, and then parse it.
  6. Notify the listener and complete the parsing

Determine path

The method uses the following method to determine whether the location is a relative path or an absolute path:

absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();

The rules for judging absolute paths are as follows:

  • An absolute path starting with classpath*: or classpath:
  • Can be constructed java.net.URLas an absolute path through this location
  • java.net.URIDetermine the call according to the location structure to isAbsolute()determine whether it is an absolute path.

Called if location is an absolute path loadBeanDefinitions(), this method is defined in AbstractBeanDefinitionReader.

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    
    
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
    
    
        throw new BeanDefinitionStoreException(
                "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    }

    if (resourceLoader instanceof ResourcePatternResolver) {
    
    
        // Resource pattern matching available.
        try {
    
    
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
            int loadCount = loadBeanDefinitions(resources);
            if (actualResources != null) {
    
    
                for (Resource resource : resources) {
    
    
                    actualResources.add(resource);
                }
            }
            if (logger.isDebugEnabled()) {
    
    
                logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
            }
            return loadCount;
        }
        catch (IOException ex) {
    
    
            throw new BeanDefinitionStoreException(
                    "Could not resolve bean definition resource pattern [" + location + "]", ex);
        }
    }
    else {
    
    
        // Can only load single resources by absolute URL.
        Resource resource = resourceLoader.getResource(location);
        int loadCount = loadBeanDefinitions(resource);
        if (actualResources != null) {
    
    
            actualResources.add(resource);
        }
        if (logger.isDebugEnabled()) {
    
    
            logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
        }
        return loadCount;
    }
}

The whole logic is relatively simple. First, obtain the ResourceLoader, and then execute different logic according to different ResourceLoaders. The main reason is that there may be multiple Resources, but they will eventually return XmlBeanDefinitionReader.loadBeanDefinitions(), so this is a recursive process.

At this point, the import tag has been parsed, and the whole process is relatively clear: get the source attribute value, get the correct resource path, and then call the loadBeanDefinitions()method to recursively load the BeanDefinition.

Guess you like

Origin blog.csdn.net/Tyson0314/article/details/133266138