Spring IoC之存储对象BeanDefinition

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Spring IoC之存储对象BeanDefinition

概述


BeanDefinition 是一个接口,它描述了一个 Bean 实例,包括属性值、构造方法值和继承自它的类的更多信息。它继承 AttributeAccessor 和 BeanMetadataElement 接口。两个接口定义如下:

  • AttributeAccessor:定义了与其他对象的元数据进行连接和访问的约定,即对属性的修改,包括获取、设置、删除。
  • BeanMetadataElement:Bean 元对象持有的配置元素可以通过 getSource()方法获取。

BeanDefinition 整个结构如下图:

image.png


BeanDefinition 在 Spring 中此接口有三种实现:RootBeanDefinition、ChildBeanDefinition 以及 GenericBeanDefinition。而这三种实现都继承了 AbstractBeanDefinition,其中 BeanDefinition 是配置文件元素标签在容器中的内部表示形式。元素标签拥有 class、scope、lazy-init 等属性,BeanDefinition 则提供了相应的 beanClass、scope、lazyInit 属性,BeanDefinition 和<bean>中的属性一一对应。其中 RootBeanDefinition 是最常用的实现类,它对应一般性的元素标签,GenericBeanDefinition 是自2.5版本以后新加入的 bean 文件配置属性定义类,是一站式服务的。


在配置文件中可以定义父和子,父用 RootBeanDefinition 表示,而子用 ChildBeanDefinition 表示,而没有父的就使用 RootBeanDefinition 表示。AbstractBeanDefinition 对两者共同的类信息进行抽象。

Spring 通过 BeanDefinition 将配置文件中的配置信息转换为容器的内部表示,并将这些 BeanDefinition 注册到 BeanDefinitionRegistry 中。Spring 容器的 BeanDefinitionRegistry 就像是 Spring 配置信息的内存数据库,主要以 map 的形式保存,后续操作直接从 BeanDefinitionRegistry 中读取配置信息。


BeanDefinition 的创建


之前我们解读了 bean 的加载过程,但是这是在 BeanDefinition 创建之后的操作,所以我们需要知道在何时何处创建的 BeanDifinition。首先看下这段代码:


@Test
public void MyBean(){
    //解析application_context.xml文件 , 生成管理相应的Bean对象
    ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
    System.out.println(context.getParent());//输出结果null
    User user = (User) context.getBean("user");
    System.out.println(user);
}
复制代码

这是在讲解 Spring源码分析之ClassPathXmlApplicationContext 一文中使用的代码,我们重新回头看一下当时解读的过程,可以发现在 obtainFreshBeanFactory()方法中提及到 BeanDefinition 的创建,具体发生在 XmlBeanDefinitionReader 类中的 doLoadBeanDefinitions(inputSource,  resource)方法中,所以接下来我们就接着对该方法进行详细的解读。


protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {
        // 获取 Document 实例
        Document doc = this.doLoadDocument(inputSource, resource);
        // 根据 Document 实例****注册 BeanDefiniton信息
        int count = this.registerBeanDefinitions(doc, resource);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    } catch (BeanDefinitionStoreException var5) {
        throw var5;
    } catch (SAXParseException var6) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
    } catch (SAXException var7) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
    } catch (ParserConfigurationException var8) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
    } catch (IOException var9) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
    } catch (Throwable var10) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
    }
}
复制代码
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
}
复制代码


实际上调用的是 DefaultDocumentLoader 类中的 loadDocument 方法,在此之前 getValidationModeForResource(resource)还获取了 xml 文件的验证模式,所以 doLoadBeanDefinitions()主要就是做了三件事情:


  1. 调用 getValidationModeForResource(resource)获取 xml 文件的验证模式;
  2. 调用 loadDocument()根据 xml 文件获取相应的 Document 实例;
  3. 调用 registerBeanDefinitions() 注册 BeanDefinition 实例


getValidationModeForResource


该方法主要是用来检查 xml 文件书写语法书写语法是否正确,要在 Spring 中使用 XSD,需要在 Spring XML 文件头部声明:


<?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">
复制代码

关于该方法的讲解,有兴趣的朋友可以查看IOC 之 获取验证模型


loadDocument


在 DocumentLoader 类中该方法定义如下:


public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isTraceEnabled()) {
        logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
    DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource);
}
复制代码


该方法接收5个参数:

  • inputSource:xml 文件被封装为 Resource 后得到的输入源,根据 Resource 类型的不同,调用各自的 getInputStream()方法;
  • entityResolver:文件解析器;
  • errorHandler:处理加载 Doucument 对象的过程中出现的错误;
  • validationMode:验证模式;
  • namespaceAware:命名空间支持,如果要提供对 XML 名称空间的支持,则为 true。


其中参数 entityResolver 通过 getEntityResolver() 获取,其定义如下:

protected EntityResolver getEntityResolver() {
    if (this.entityResolver == null) {
        ResourceLoader resourceLoader = this.getResourceLoader();
        if (resourceLoader != null) {
            this.entityResolver = new ResourceEntityResolver(resourceLoader);
        } else {
            this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
        }
    }
    return this.entityResolver;
}
复制代码


如果当前 BeanDefinitionReader下的 resourceLoader 属性不为 null,则根据指定的 resourceLoader 创建一个 ResourceEntityResolver。如果 resourceLoader 为null,则创建 一个 DelegatingEntityResolver,该 Resolver 委托给默认的 BeansDtdResolver 和 PluggableSchemaResolver 。


  • ResourceEntityResolver:继承自 DelegatingEntityResolver,通过 ResourceLoader 来解析实体的引用。
  • DelegatingEntityResolver:EntityResolver 的实现,分别代理了 dtd 的 BeansDtdResolver 和 xsd 的 PluggableSchemaResolver。
  • BeansDtdResolver:Spring Bean DTD的 EntityResolver 实现,用于从Spring类路径resp加载DTD、JAR文件。 从类路径资源“ /org/springframework/beans/factory/xml/spring-beans.dtd”中获取“ spring-beans.dtd”,无论是将其指定为某些本地URL还是将其指定为“ http://www.springframework”、“org / dtd / spring-beans.dtd”。
  • PluggableSchemaResolver:使用一系列 Map 文件将 schema url 解析到本地 classpath 资源


关于 EntityResolver 的介绍,可以参考SAX EntityResolver 的作用

loadDocument 方法中首先调用createDocumentBuilderFactory() 创建 DocumentBuilderFactory ,再通过该 factory 创建 DocumentBuilder,最后解析 InputSource 返回 Document 对象。核心方法为 parse()方法,具体实现在 DocumentBuilderImpl 类中,其定义如下:


public Document parse(InputSource is) throws SAXException, IOException {
    if (is == null) {
        throw new IllegalArgumentException(DOMMessageFormatter.formatMessage("http://www.w3.org/dom/DOMTR", "jaxp-null-input-source", (Object[])null));
    } else {
        if (this.fSchemaValidator != null) {
            if (this.fSchemaValidationManager != null) {
                this.fSchemaValidationManager.reset();
                this.fUnparsedEntityHandler.reset();
            }
            this.resetSchemaValidator();
        }
        this.domParser.parse(is);
        Document doc = this.domParser.getDocument();
        this.domParser.dropDocumentReferences();
        return doc;
    }
}
复制代码


该方法用于加载配置文件,并解析成w3c的 Document 对象,用于后续创建 BeanDefinition。其内容如下:

image.png


registerBeanDefinitions


该方法定义如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
    int countBefore = this.getRegistry().getBeanDefinitionCount();
    documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
    return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
复制代码


该方法先创建一个 DefaultBeanDefinitionDocumentReader 对象,然后调用其 registerBeanDefinitions 方法,转而进入到 doRegisterBeanDefinitions()方法。


protected void doRegisterBeanDefinitions(Element root) {
    //获取当前类所包含的BeanDefinitionParserDelegate属性,作为父类生成新的组件
    BeanDefinitionParserDelegate parent = this.delegate;
    //如果父类为null,组件初始化默认的lazy-init,autowire,依赖项检查设置,init-method,destroy-method和merge设置。
    this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
    //确定给定节点是否指示默认名称空间,通过比对节点包含的uri和指定的uri是否相同
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute("profile");
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                }
                return;
            }
        }
    }
    this.preProcessXml(root);
    this.parseBeanDefinitions(root, this.delegate);
    this.postProcessXml(root);
    this.delegate = parent;
}
复制代码


BeanDefinitionParserDelegate  用于解析XML bean定义,preProcessXml()方法在当前类中属于空方法,重点是 parseBeanDefinitions(root, this.delegate)


protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 判断根节点使用的标签所对应的命名空间是否为Spring提供的默认命名空间,
    // 这里根节点为beans节点,该节点的命名空间通过其xmlns属性进行了定义
    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)) {
                    // 当前标签使用的是默认的命名空间,如bean标签,
                    // 则按照默认命名空间的逻辑对其进行处理
                    this.parseDefaultElement(ele, delegate);
                } else {
                    // 判断当前标签使用的命名空间是自定义的命名空间
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        // 如果根节点使用的命名空间不是默认的命名空间,则按照自定义的命名空间进行处理
        delegate.parseCustomElement(root);
    }
}
复制代码


该方法用来遍历 root 节点下的子节点,比如说 root 节点为节点,则遍历它所包含的、等节点。如果根节点或者子节点采用默认命名空间的话,则调用 parseDefaultElement() 进行默认标签解析,否则调用 delegate.parseCustomElement() 方法进行自定义解析。 接下来我们先研究 parseDefaultElement()方法,其定义如下:


private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if (delegate.nodeNameEquals(ele, "import")) {
        this.importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, "alias")) {
        this.processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, "bean")) {
        this.processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, "beans")) {
        this.doRegisterBeanDefinitions(ele);
    }
}
复制代码


方法的功能一目了然,分别是对四种不同的标签进行解析,分别是 import、alias、bean、beans。


对应 bean 标签的解析是最核心的功能,对于 alias、import、beans 标签的解析都是基于 bean 标签解析的。


import 标签


当项目工程比较大,需要维护的配置文件也会更加复杂,数量也会更多,由于维护的人员分工不同,可能会产生很多个配置文件,这个时候如果想要将所有的配置文件整合到一个 XML 文件中,就需要利用 import 标签。例如我们可以构造一个这样的 application_context.xml。


<?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">
    <import resource="spring-student.xml"/>
    <import resource="spring-user.xml"/>
</beans>
复制代码


application_context.xml  配置文件中使用 import 标签的方式导入其他模块的配置文件,如果有配置需要修改直接修改相应配置文件即可,若有新的模块需要引入直接增加 import 即可,这样大大简化了配置后期维护的复杂度,同时也易于管理。回到代码中,Spring 利用 importBeanDefinitionResource() 方法完成对 import 标签的解析。


protected void importBeanDefinitionResource(Element ele) {
    // 获取 resource 的属性值 
    String location = ele.getAttribute("resource");
    //判空处理
    if (!StringUtils.hasText(location)) {
        this.getReaderContext().error("Resource location must not be empty", ele);
    } else {
        // 解析系统属性,格式如 :"${user.dir}",也就是占位符替换
        location = this.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 var11) {
        }
        int importCount;
        // 绝对路径
        if (absoluteLocation) {
            try {
                // 直接根据地质加载相应的配置文件
                importCount = this.getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
                }
            } catch (BeanDefinitionStoreException var10) {
                this.getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, var10);
            }
        } else {
            try {
                // 相对路径则根据相应的地质计算出绝对路径地址
                Resource relativeResource = this.getReaderContext().getResource().createRelative(location);
                if (relativeResource.exists()) {
                    importCount = this.getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                    actualResources.add(relativeResource);
                } else {
                    String baseLocation = this.getReaderContext().getResource().getURL().toString();
                    importCount = this.getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);
                }
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
                }
            } catch (IOException var8) {
                this.getReaderContext().error("Failed to resolve current resource location", ele, var8);
            } catch (BeanDefinitionStoreException var9) {
                this.getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, var9);
            }
        }
        // 解析成功后,进行监听器激活处理
        Resource[] actResArray = (Resource[])actualResources.toArray(new Resource[0]);
        this.getReaderContext().fireImportProcessed(location, actResArray, this.extractSource(ele));
    }
}
复制代码


解析 import 过程较为清晰,整个过程如下:

  1. 获取 source 属性的值,该值表示资源的路径
  2. 解析路径中的系统属性,如"${user.dir}"
  3. 判断资源路径 location 是绝对路径还是相对路径
  4. 如果是绝对路径,则调递归调用 Bean 的解析过程,进行另一次的解析
  5. 如果是相对路径,则先计算出绝对路径得到 Resource,然后进行解析
  6. 通知监听器,完成解析


判断路径 方法通过以下方法来判断 location 是为相对路径还是绝对路径:


absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
复制代码


判断绝对路径的规则如下:

  • 以 classpath*: 或者 classpath: 开头为绝对路径
  • 能够通过该 location 构建出 java.net.URL为绝对路径
  • 根据 location 构造 java.net.URI 判断调用 isAbsolute() 判断是否为绝对路径


绝对路径 如果 location 为绝对路径则调用 loadBeanDefinitions(),该方法在 AbstractBeanDefinitionReader 中定义。


public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    ResourceLoader resourceLoader = this.getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    } else {
        int count;
        if (resourceLoader instanceof ResourcePatternResolver) {
            try {
                Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                count = this.loadBeanDefinitions(resources);
                if (actualResources != null) {
                    Collections.addAll(actualResources, resources);
                }
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                }
                return count;
            } catch (IOException var6) {
                throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6);
            }
        } else {
            Resource resource = resourceLoader.getResource(location);
            count = this.loadBeanDefinitions((Resource)resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
            }
            return count;
        }
    }
}
复制代码


整个逻辑比较简单,首先获取 ResourceLoader,然后根据不同的 ResourceLoader 执行不同的逻辑,主要是可能存在多个 Resource,但是最终都会回归到 XmlBeanDefinitionReader.loadBeanDefinitions() ,所以这是一个递归的过程。


相对路径 如果是相对路径则会根据相应的 Resource 计算出相应的绝对路径,然后根据该路径构造一个 Resource,若该 Resource 存在,则调用 XmlBeanDefinitionReader.loadBeanDefinitions() 进行 BeanDefinition 加载,否则构造一个绝对 location ,调用 AbstractBeanDefinitionReader.loadBeanDefinitions() 方法,与绝对路径过程一样。


至此,import 标签解析完毕,整个过程比较清晰明了:获取 source 属性值,得到正确的资源路径,然后调用 loadBeanDefinitions() 方法进行递归的 BeanDefinition 加载。


alias 标签



对 bean 进行定义时,除了用id来 指定名称外,为了提供多个名称,可以使用 name 属性来指定。而所有这些名称都指向同一个 bean。在 XML 配置文件中,可用单独的元素来完成 bean 别名的定义。我们可以直接使用 bean 标签中的 name 属性,如下:


<bean id="user" class="com.msdn.bean.User" name="user2,user3">
    <constructor-arg name="name" value="hresh" />
</bean>
复制代码


在 Spring 还可以使用 alias 来声明别名:


<bean id="user" class="com.msdn.bean.User" />
<alias name="user" alias="user2,user3"/>
复制代码


我们具体看下alias标签的解析过程,解析使用的方法 processAliasRegistration(ele),方法体如下:


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


通过代码可以发现主要是将 beanName 与别名 alias 组成一对注册到 registry 中。跟踪代码最终使用了 SimpleAliasRegistry 中的 registerAlias(String name, String alias)方法 。


public void resolveAliases(StringValueResolver valueResolver) {
    Assert.notNull(valueResolver, "StringValueResolver must not be null");
    synchronized(this.aliasMap) {
        Map<String, String> aliasCopy = new HashMap(this.aliasMap);
        aliasCopy.forEach((alias, registeredName) -> {
            String resolvedAlias = valueResolver.resolveStringValue(alias);
            String resolvedName = valueResolver.resolveStringValue(registeredName);
            if (resolvedAlias != null && resolvedName != null && !resolvedAlias.equals(resolvedName)) {
                if (!resolvedAlias.equals(alias)) {
                    String existingName = (String)this.aliasMap.get(resolvedAlias);
                    if (existingName != null) {
                        if (existingName.equals(resolvedName)) {
                            this.aliasMap.remove(alias);
                            return;
                        }
                        throw new IllegalStateException("Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias + "') for name '" + resolvedName + "': It is already registered for name '" + registeredName + "'.");
                    }
                    //当A->B存在时,若再次出现A->C->B时候则会抛出异常。
                    this.checkForAliasCircle(resolvedName, resolvedAlias);
                    this.aliasMap.remove(alias);
                    this.aliasMap.put(resolvedAlias, resolvedName);
                } else if (!registeredName.equals(resolvedName)) {
                    this.aliasMap.put(alias, resolvedName);
                }
            } else {
                this.aliasMap.remove(alias);
            }
        });
    }
}
复制代码


上述代码的流程总结如下:

  • alias 与 beanName 相同则不需要处理并删除原有的 alias;
  • alias 覆盖处理。 若 aliasName 已经使用并已经指向了另一 beanName 则需要用户的设置进行处理;
  • alias 循环检查,当A->B存在时,若再次出现A->C->B时候则会抛出异常。


bean 标签


Spring 中最复杂也是最重要的是 bean 标签的解析过程。 在方法 parseDefaultElement() 中,如果遇到标签 为 bean 则调用 processBeanDefinition() 方法进行 bean 标签解析,如下:


protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
        } catch (BeanDefinitionStoreException var5) {
            this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
        }
        this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}
复制代码


整个过程分为四步:

  1. 首先委托 BeanDefinitionDelegate 类的 parseBeanDefinitionElement 方法进行元素的解析,返回 BeanDefinitionHolder 类型的实例 bdHolder,经过这个方法后bdHolder 实例已经包含了我们配置文件中的各种属性了,例如 class,name,id,alias 等。
  2. 当返回的 dbHolder 不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
  3. 当解析完成后,需要对解析后的 bdHolder 进行注册,注册过程委托给了BeanDefinitionReaderUtils 的 registerBeanDefinition 方法。
  4. 最后发出响应事件,通知相关的监听器已经加载完这个Bean了。


parseBeanDefinitionElement

接下来我们就针对具体的方法进行分析,首先我们从元素解析及信息提取开始,首先进入 BeanDefinitionParserDelegate 类的 parseBeanDefinitionElement 方法。


public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    String id = ele.getAttribute("id");
    String nameAttr = ele.getAttribute("name");
    List<String> aliases = new ArrayList();
    if (StringUtils.hasLength(nameAttr)) {
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; ");
        aliases.addAll(Arrays.asList(nameArr));
    }
    String beanName = id;
    if (!StringUtils.hasText(id) && !aliases.isEmpty()) {
        beanName = (String)aliases.remove(0);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");
        }
    }
    // 检查 name 的唯一性
    if (containingBean == null) {
        this.checkNameUniqueness(beanName, aliases, ele);
    }
    // 解析属性,构造 AbstractBeanDefinition
    AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        // 如果 beanName 不存在,则根据条件构造一个 beanName
        if (!StringUtils.hasText(beanName)) {
            try {
                if (containingBean != null) {
                    beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
                } else {
                    beanName = this.readerContext.generateBeanName(beanDefinition);
                    String beanClassName = beanDefinition.getBeanClassName();
                    if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        aliases.add(beanClassName);
                    }
                }
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Neither XML 'id' nor 'name' specified - using generated bean name [" + beanName + "]");
                }
            } catch (Exception var9) {
                this.error(var9.getMessage(), ele);
                return null;
            }
        }
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        // 封装 BeanDefinitionHolder
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    } else {
        return null;
    }
}
复制代码


上述方法就是对默认标签解析的全过程了,我们分析下当前层完成的工作:


  • 提取标签中的 id 和 name 属性;
  • 进一步解析其它所有属性并统一封装到 GenericBeanDefinition 类型的实例中;
  • 如果检测到 bean 没有指定 beanName,那么使用默认规则为此 bean 生成的 beanName;
  • 将获取到的数据封装到 BeanDefinitionHolder的实例中。


parseBeanDefinitionElement(ele, beanName, containingBean)方法用来解析标签中的属性,源码如下:


public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {
    this.parseState.push(new BeanEntry(beanName));
    String className = null;
    if (ele.hasAttribute("class")) {
        className = ele.getAttribute("class").trim();
    }
    String parent = null;
    if (ele.hasAttribute("parent")) {
        parent = ele.getAttribute("parent");
    }
    try {
        //创建用于承载属性的AbstractBeanDefinition类型的GenericBeanDefinition实例
        AbstractBeanDefinition bd = this.createBeanDefinition(className, parent);
        //硬编码解析bean的各种属性
        this.parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        //设置description属性
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, "description"));
        //解析元素
        this.parseMetaElements(ele, bd);
        //解析lookup-method属性
        this.parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        //解析replace-method属性
        this.parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
        //解析构造函数的参数
        this.parseConstructorArgElements(ele, bd);
        //解析properties子元素
        this.parsePropertyElements(ele, bd);
        //解析qualifier子元素
        this.parseQualifierElements(ele, bd);
        bd.setResource(this.readerContext.getResource());
        bd.setSource(this.extractSource(ele));
        AbstractBeanDefinition var7 = bd;
        return var7;
    } catch (ClassNotFoundException var13) {
        this.error("Bean class [" + className + "] not found", ele, var13);
    } catch (NoClassDefFoundError var14) {
        this.error("Class that bean class [" + className + "] depends on not found", ele, var14);
    } catch (Throwable var15) {
        this.error("Unexpected failure during bean definition parsing", ele, var15);
    } finally {
        this.parseState.pop();
    }
    return null;
}
复制代码


BeanDefinitionParserDelegate.parseBeanDefinitionElement() 中完成 Bean 的解析,返回的是一个已经完成对标签解析的 BeanDefinition 实例。在该方法内部,首先调用 createBeanDefinition() 方法创建一个用于承载属性的 GenericBeanDefinition 实例,如下:


protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName) throws ClassNotFoundException {
    return BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader());
}
复制代码


实现细节在 BeanDefinitionReaderUtils 类中,如下:


public static AbstractBeanDefinition createBeanDefinition(@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
    GenericBeanDefinition bd = new GenericBeanDefinition();
    bd.setParentName(parentName);
    if (className != null) {
        if (classLoader != null) {
            bd.setBeanClass(ClassUtils.forName(className, classLoader));
        } else {
            bd.setBeanClassName(className);
        }
    }
    return bd;
}
复制代码


该方法主要是设置 parentName、className、classLoader。 创建完 GenericBeanDefinition 实例后,再调用 parseBeanDefinitionAttributes() ,该方法将创建好的 GenericBeanDefinition 实例当做参数,对 Bean 标签的所有属性进行解析,如下:


public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
    if (ele.hasAttribute("singleton")) {
        this.error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
    } else if (ele.hasAttribute("scope")) {
        bd.setScope(ele.getAttribute("scope"));
    } else if (containingBean != null) {
        bd.setScope(containingBean.getScope());
    }
    if (ele.hasAttribute("abstract")) {
        bd.setAbstract("true".equals(ele.getAttribute("abstract")));
    }
    String lazyInit = ele.getAttribute("lazy-init");
    if (this.isDefaultValue(lazyInit)) {
        lazyInit = this.defaults.getLazyInit();
    }
    bd.setLazyInit("true".equals(lazyInit));
    String autowire = ele.getAttribute("autowire");
    bd.setAutowireMode(this.getAutowireMode(autowire));
    String autowireCandidate;
    if (ele.hasAttribute("depends-on")) {
        autowireCandidate = ele.getAttribute("depends-on");
        bd.setDependsOn(StringUtils.tokenizeToStringArray(autowireCandidate, ",; "));
    }
    autowireCandidate = ele.getAttribute("autowire-candidate");
    String destroyMethodName;
    if (this.isDefaultValue(autowireCandidate)) {
        destroyMethodName = this.defaults.getAutowireCandidates();
        if (destroyMethodName != null) {
            String[] patterns = StringUtils.commaDelimitedListToStringArray(destroyMethodName);
            bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
        }
    } else {
        bd.setAutowireCandidate("true".equals(autowireCandidate));
    }
    if (ele.hasAttribute("primary")) {
        bd.setPrimary("true".equals(ele.getAttribute("primary")));
    }
    if (ele.hasAttribute("init-method")) {
        destroyMethodName = ele.getAttribute("init-method");
        bd.setInitMethodName(destroyMethodName);
    } else if (this.defaults.getInitMethod() != null) {
        bd.setInitMethodName(this.defaults.getInitMethod());
        bd.setEnforceInitMethod(false);
    }
    if (ele.hasAttribute("destroy-method")) {
        destroyMethodName = ele.getAttribute("destroy-method");
        bd.setDestroyMethodName(destroyMethodName);
    } else if (this.defaults.getDestroyMethod() != null) {
        bd.setDestroyMethodName(this.defaults.getDestroyMethod());
        bd.setEnforceDestroyMethod(false);
    }
    if (ele.hasAttribute("factory-method")) {
        bd.setFactoryMethodName(ele.getAttribute("factory-method"));
    }
    if (ele.hasAttribute("factory-bean")) {
        bd.setFactoryBeanName(ele.getAttribute("factory-bean"));
    }
    return bd;
}
复制代码


从上面代码我们可以清晰地看到对 Bean 标签属性的解析,这些属性我们或多或少用到过。 完成 Bean 标签基本属性解析后,会依次调用 parseMetaElements()parseLookupOverrideSubElements()parseReplacedMethodSubElements() 对子元素 meta、lookup-method、replace-method 完成解析。关于这三个属性的解析,可以参考IOC 之解析 bean 标签:meta、lookup-method、replace-method。之后还有对 constructor-arg 、property、qualifier 三个子元素的解析,同样可以参考IOC 之解析 bean 标签:constructor-arg、property 子元素


decorateBeanDefinitionIfRequired


parseBeanDefinitionElement()方法完成默认标签的解析之后,如果解析成功,则首先调用 BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired() 完成自定义标签元素解析。关于自定义标签的解析分为两种: Spring中默认标签下的自定义标签自定义标签中不同标签的处理器,这些都是以标签所提供的命名空间为基础的,相关实现方式会在之后的文章做介绍。接下来我们所介绍的是标签下的自定义标签。

具体实现是在 BeanDefinitionParserDelegate 类中,


public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
    return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
    BeanDefinitionHolder finalDefinition = originalDef;
    NamedNodeMap attributes = ele.getAttributes();
    // 遍历节点,查看是否有适用于装饰的属性
    for(int i = 0; i < attributes.getLength(); ++i) {
        Node node = attributes.item(i);
        finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd);
    }
    NodeList children = ele.getChildNodes();
    // 遍历子节点,查看是否有适用于修饰的子元素
    for(int i = 0; i < children.getLength(); ++i) {
        Node node = children.item(i);
        if (node.getNodeType() == 1) {
            finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd);
        }
    }
    return finalDefinition;
}
复制代码


遍历节点(子节点),调用 decorateIfRequired() 装饰节点(子节点)。


public BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
    // 获取自定义标签的命名空间
    String namespaceUri = this.getNamespaceURI(node);
    // 过滤掉默认命名标签
    if (namespaceUri != null && !this.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/schema/")) {
            this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
        }
    }
    return originalDef;
}
复制代码


首先获取自定义标签的命名空间,如果不是默认的命名空间则根据该命名空间获取相应的处理器,最后调用处理器的 decorate()方法进行装饰处理,如下是该方法的实现:


public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
    // 获取当前自定义属性或子标签注册的BeanDefinitionDecorator对象
    BeanDefinitionDecorator decorator = this.findDecoratorForNode(node, parserContext);
    // 调用自定义的BeanDefinitionDecorator.decorate()方法进行装饰,
    // 这里就是我们实现的UserDefinitionDecorator类,该案例在下一篇文章中
    return decorator != null ? decorator.decorate(node, definition, parserContext) : null;
}
复制代码


自定义属性或自定义子标签查找当前 Decorator 的方法是需要对属性或子标签进行分别判断的,如下是 findDecoratorForNode()的实现:


private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) {
    BeanDefinitionDecorator decorator = null;
    // 获取当前标签或属性的局部键名
    String localName = parserContext.getDelegate().getLocalName(node);
    // 判断当前节点是属性还是子标签,根据情况不同获取不同的Decorator处理逻辑
    if (node instanceof Element) {
        decorator = (BeanDefinitionDecorator)this.decorators.get(localName);
    } else if (node instanceof Attr) {
        decorator = (BeanDefinitionDecorator)this.attributeDecorators.get(localName);
    } else {
        parserContext.getReaderContext().fatal("Cannot decorate based on Nodes of type [" + node.getClass().getName() + "]", node);
    }
    if (decorator == null) {
        parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionDecorator for " + (node instanceof Element ? "element" : "attribute") + " [" + localName + "]", node);
    }
    return decorator;
}
复制代码


对于 BeanDefinitionDecorator 处理逻辑的查找,可以看到,其会根据节点的类型进行判断,根据不同的情况获取不同的 BeanDefinitionDecorator 处理对象。


上述代码适用于默认标签下的自定义子标签或自定义属性,关于这两者的使用方法,我会在下一篇文章进行代码展示。


BeanDefinitionReaderUtils.registerBeanDefinition() 对 bdHolder 进行注册,我们看一下其具体实现。


public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
    //获取bean定义的名称
    String beanName = definitionHolder.getBeanName();
    //将 beanName与实例化的BeanDefinition绑定在一起
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    //如果有别名,则将beanName与别名相关联
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        String[] var4 = aliases;
        int var5 = aliases.length;
        for(int var6 = 0; var6 < var5; ++var6) {
            String alias = var4[var6];
            registry.registerAlias(beanName, alias);
        }
    }
}
复制代码


beans 标签


该标签就是再次调用 doRegisterBeanDefinitions()方法,查找默认标签或自定义标签。


自定义标签


关于自定义标签的解读在下一章节学习,有兴趣的朋友可以阅读一下:Spring IoC自定义标签解析



目录
相关文章
|
4天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
3天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
3天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
24天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
45 2
|
16天前
|
XML 安全 Java
Spring Boot中使用MapStruct进行对象映射
本文介绍如何在Spring Boot项目中使用MapStruct进行对象映射,探讨其性能高效、类型安全及易于集成等优势,并详细说明添加MapStruct依赖的步骤。
|
1月前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
44 0
|
2月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
80 0
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
245 2