spring源码深度解析— IOC 之 默认标签解析(下)
在spring源码深度解析— ioc 之 默认标签解析(上)中我们已经完成了从xml配置文件到beandefinition的转换,转换后的实例是genericbeandefinition的实例。本文主要来看看标签解析剩余部分及beandefinition的注册。
默认标签中的自定义标签解析
在上篇博文中我们已经分析了对于默认标签的解析,我们继续看戏之前的代码,如下图片中有一个方法:delegate.decoratebeandefinitionifrequired(ele, bdholder)
这个方法的作用是什么呢?首先我们看下这种场景,如下配置文件:
<bean id="demo" class="com.chenhao.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.chenhao.spring.mytestbean" name="demo1,demo2"> <property name="beanname" value="bean demo1"/> </bean>
在spring还有另外一种声明别名的方式:
<bean id="mytestbean" class="com.chenhao.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.chenhao.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 加载。
上一篇: 关系有点复杂
推荐阅读
-
spring源码深度解析— IOC 之 默认标签解析(上)
-
spring源码深度解析— IOC 之 循环依赖处理
-
【死磕 Spring】----- IOC 之解析 bean 标签:开启解析进程
-
spring源码深度解析— IOC 之 开启 bean 的加载
-
spring源码深度解析— IOC 之 默认标签解析(下)
-
spring源码深度解析— IOC 之 bean 的初始化
-
【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)
-
【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)
-
spring源码深度解析— IOC 之 属性填充
-
spring源码深度解析— IOC 之 默认标签解析(上)