概述
BeanDefinition 是一个接口,它描述了一个 Bean 实例,包括属性值、构造方法值和继承自它的类的更多信息。它继承 AttributeAccessor 和 BeanMetadataElement 接口。两个接口定义如下:
- AttributeAccessor:定义了与其他对象的元数据进行连接和访问的约定,即对属性的修改,包括获取、设置、删除。
- BeanMetadataElement:Bean 元对象持有的配置元素可以通过
getSource()
方法获取。
BeanDefinition 整个结构如下图:
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()
主要就是做了三件事情:
- 调用
getValidationModeForResource(resource)
获取 xml 文件的验证模式; - 调用
loadDocument()
根据 xml 文件获取相应的 Document 实例; - 调用
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。其内容如下:
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 过程较为清晰,整个过程如下:
- 获取 source 属性的值,该值表示资源的路径
- 解析路径中的系统属性,如"${user.dir}"
- 判断资源路径 location 是绝对路径还是相对路径
- 如果是绝对路径,则调递归调用 Bean 的解析过程,进行另一次的解析
- 如果是相对路径,则先计算出绝对路径得到 Resource,然后进行解析
- 通知监听器,完成解析
判断路径 方法通过以下方法来判断 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)); } } 复制代码
整个过程分为四步:
- 首先委托 BeanDefinitionDelegate 类的 parseBeanDefinitionElement 方法进行元素的解析,返回 BeanDefinitionHolder 类型的实例 bdHolder,经过这个方法后bdHolder 实例已经包含了我们配置文件中的各种属性了,例如 class,name,id,alias 等。
- 当返回的 dbHolder 不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
- 当解析完成后,需要对解析后的 bdHolder 进行注册,注册过程委托给了BeanDefinitionReaderUtils 的 registerBeanDefinition 方法。
- 最后发出响应事件,通知相关的监听器已经加载完这个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自定义标签解析