注释
在上一篇咱们曾经实现了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例。本文次要来看看标签解析残余局部及BeanDefinition的注册。
默认标签中的自定义标签解析
在上篇博文中咱们曾经剖析了对于默认标签的解析,咱们持续看戏之前的代码,如下图片中有一个办法:delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)
这个办法的作用是什么呢?首先咱们看下这种场景,如下配置文件:
<bean id="demo" class="com.dabin.spring.MyTestBean"> <property name="beanName" value="bean demo1"/> <meta key="demo" value="demo"/> <mybean:username="mybean"/> </bean>
这个配置文件中有个自定义的标签,decorateBeanDefinitionIfRequired办法就是用来解决这种状况的,其中的null是用来传递父级BeanDefinition的,咱们进入到其办法体:
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;}
咱们看到下面的代码有两个遍历操作,一个是用于对所有的属性进行遍历解决,另一个是对所有的子节点进行解决,两个遍历操作都用到了decorateIfRequired(node, finalDefinition, containingBd);办法,咱们持续跟踪代码,进入办法体:
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));}
首先获取自定义标签的命名空间,如果不是默认的命名空间则依据该命名空间获取相应的处理器,最初调用处理器的 decorate()
进行装璜解决。具体的装璜过程这里不进行讲述,在前面剖析自定义标签时会做具体阐明。
注册解析的BeanDefinition
对于配置文件,解析和装璜实现之后,对于失去的beanDefinition曾经能够满足后续的应用要求了,还剩下注册,也就是processBeanDefinition函数中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry())代码的解析了。进入办法体:
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); } }}
从下面的代码咱们看到是用了beanName作为惟一标示进行注册的,而后注册了所有的别名aliase。而beanDefinition最终都是注册到BeanDefinitionRegistry中,接下来咱们具体看下注册流程。
通过beanName注册BeanDefinition
在spring中除了应用beanName作为key将BeanDefinition放入Map中还做了其余一些事件,咱们看下办法registerBeanDefinition代码,BeanDefinitionRegistry是一个接口,他有三个实现类,DefaultListableBeanFactory、SimpleBeanDefinitionRegistry、GenericApplicationContext,其中SimpleBeanDefinitionRegistry非常简单,而GenericApplicationContext最终也是应用的DefaultListableBeanFactory中的实现办法,咱们看下代码:
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); }}
处理过程如下:
- 首先 BeanDefinition 进行校验,该校验也是注册过程中的最初一次校验了,次要是对 AbstractBeanDefinition 的 methodOverrides 属性进行校验
- 依据 beanName 从缓存中获取 BeanDefinition,如果缓存中存在,则依据 allowBeanDefinitionOverriding 标记来判断是否容许笼罩,如果容许则间接笼罩,否则抛出 BeanDefinitionStoreException 异样
- 若缓存中没有指定 beanName 的 BeanDefinition,则判断以后阶段是否曾经开始了 Bean 的创立阶段(),如果是,则须要对 beanDefinitionMap 进行加锁管制并发问题,否则间接设置即可。对于
hasBeanCreationStarted()
办法后续做具体介绍,这里不过多论述。 - 若缓存中存在该 beanName 或者 单利 bean 汇合中存在该 beanName,则调用
resetBeanDefinition()
重置 BeanDefinition 缓存。
其实整段代码的外围就在于 this.beanDefinitionMap.put(beanName, beanDefinition);
。BeanDefinition 的缓存也不是神奇的货色,就是定义 一个 ConcurrentHashMap,key 为 beanName,value 为 BeanDefinition。
通过别名注册BeanDefinition
通过别名注册BeanDefinition最终是在SimpleBeanDefinitionRegistry中实现的,咱们看下代码:
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); } }}
上述代码的流程总结如下:
(1)alias与beanName雷同状况解决,若alias与beanName并名称雷同则不须要解决并删除原有的alias
(2)alias笼罩解决。若aliasName曾经应用并曾经指向了另一beanName则须要用户的设置进行解决
(3)alias循环查看,当A->B存在时,若再次出现A->C->B时候则会抛出异样。
alias标签的解析
对应bean标签的解析是最外围的性能,对于alias、import、beans标签的解析都是基于bean标签解析的,接下来我就剖析下alias标签的解析。咱们回到 parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)办法,持续看下办法体,如下图所示:
对bean进行定义时,除了用id来 指定名称外,为了提供多个名称,能够应用alias标签来指定。而所有这些名称都指向同一个bean。在XML配置文件中,可用独自的元素来实现bean别名的定义。咱们能够间接应用bean标签中的name属性,如下:
<bean id="demo" class="com.dabin.spring.MyTestBean" name="demo1,demo2"> <property name="beanName" value="bean demo1"/></bean>
在Spring还有另外一种申明别名的形式:
<bean id="myTestBean" class="com.dabin.spring.MyTestBean"/><alias name="myTestBean" alias="testBean1,testBean2"/>
咱们具体看下alias标签的解析过程,解析应用的办法processAliasRegistration(ele),办法体如下:
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)); }}
通过代码能够发现解析流程与bean中的alias解析大同小异,都是讲beanName与别名alias组成一对注册到registry中。跟踪代码最终应用了SimpleAliasRegistry中的registerAlias(String name, String alias)办法
import标签的解析
对于Spring配置文件的编写,经验过大型项目的人都晓得,外面有太多的配置文件了。根本采纳的形式都是分模块,分模块的形式很多,应用import就是其中一种,例如咱们能够结构这样的Spring配置文件:
<?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>
applicationContext.xml文件中应用import形式导入有模块配置文件,当前若有新模块入加,那就能够简略批改这个文件了。这样大大简化了配置前期保护的复杂度,并使配置模块化,易于治理。咱们来看看Spring是如何解析import配置文件的呢。解析import标签应用的是importBeanDefinitionResource(ele),进入办法体:
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));}
解析 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 = 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; }}
整个逻辑比较简单,首先获取 ResourceLoader,而后依据不同的 ResourceLoader 执行不同的逻辑,次要是可能存在多个 Resource,然而最终都会回归到 XmlBeanDefinitionReader.loadBeanDefinitions()
,所以这是一个递归的过程。
至此,import 标签解析结束,整个过程比拟清晰明了:获取 source 属性值,失去正确的资源门路,而后调用 loadBeanDefinitions()
办法进行递归的 BeanDefinition 加载。