详解Spring简单容器中的Bean基本加载过程
本篇将对定义在 xml 文件中的 bean,从静态的的定义到变成可以使用的对象的过程,即 bean 的加载和获取的过程进行一个整体的了解,不去深究,点到为止,只求对 spring ioc 的实现过程有一个整体的感知,具体实现细节留到后面用针对性的篇章进行讲解。
首先我们来引入一个 spring 入门使用示例,假设我们现在定义了一个类 org.zhenchao.framework.mybean
,我们希望利用 spring 来管理类对象,这里我们利用 spring 经典的 xml 配置文件形式进行配置:
<?xml version="1.0" encoding="utf-8"?> <beansxmlns="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的基本配置 --> <beanname="mybean"class="org.zhenchao.framework.mybean"/> </beans>
我们将上面的配置文件命名为 spring-core.xml,则对象的最原始的获取和使用示例如下:
// 1. 定义资源 resource resource = new classpathresource("spring-core.xml"); // 2. 利用xmlbeanfactory解析并注册bean定义 xmlbeanfactory beanfactory = new xmlbeanfactory(resource); // 3. 从ioc容器加载获取bean mybean mybean = (mybean) beanfactory.getbean("mybean"); // 4. 使用bean mybean.sayhello();
上面 demo 虽然简单,但麻雀虽小,五脏俱全,完整的让 spring 执行了一遍配置文件加载,并获取 bean 的过程。虽然从 spring 3.1 开始 xmlbeanfactory 已经被置为 deprecated
,但是 spring 并没有定义出更加高级的基于 xml 加载 bean 的 beanfactory,而是推荐采用更加原生的方式,即组合使用 defaultlistablebeanfactory
和 xmlbeandefinitionreader
来完成上诉过程:
resource resource = new classpathresource("spring-core.xml"); defaultlistablebeanfactory beanfactory = new defaultlistablebeanfactory(); xmlbeandefinitionreader reader = new xmlbeandefinitionreader(beanfactory); reader.loadbeandefinitions(resource); mybean mybean = (mybean) beanfactory.getbean("mybean"); mybean.sayhello();
后面的分析你将会看到 xmlbeanfactory 实际上是对 defaultlistablebeanfactory 和 xmlbeandefinitionreader 组合使用方式的封装,所以这里我们仍然将继续分析基于 xmlbeanfactory 加载 bean 的过程。
一. bean的解析和注册
bean的加载过程,主要是对配置文件的解析,并注册 bean 的过程,上图是加载过程的时序图,当我们 new xmlbeanfactory(resource)
的时候,已经完成将配置文件包装成了 spring 定义的资源,并触发解析和注册。 new xmlbeanfactory(resource)
调用的是下面的构造方法:
publicxmlbeanfactory(resource resource)throwsbeansexception{ this(resource, null); }
这个构造方法本质上还是继续调用了:
publicxmlbeanfactory(resource resource, beanfactory parentbeanfactory)throwsbeansexception{ super(parentbeanfactory); // 加载xml资源 this.reader.loadbeandefinitions(resource); }
在这个构造方法里面先是调用了父类构造函数,即 org.springframework.beans.factory.support.defaultlistablebeanfactory
类,这是一个非常核心的类,它包含了基本 ioc 容器所具有的重要功能,是一个 ioc 容器的基本实现。然后是调用了 this.reader.loadbeandefinitions(resource)
,从这里开始加载配置文件。
spring 在设计采用了许多程序设计的基本原则,比如迪米特法则、开闭原则,以及接口隔离原则等等,这样的设计为后续的扩展提供了灵活性,也增强了模块的复用性,这也是我看 spring 源码的动力之一,希望通过阅读学习的过程来提升自己接口设计的能力。spring 使用了专门的资源加载器对资源进行加载,这里的 reader 就是 org.springframework.beans.factory.xml.xmlbeandefinitionreader
对象,专门用来加载基于 xml 文件配置的 bean。这里的加载过程为:
- 利用 encodedresource 二次包装资源文件
- 获取资源输入流,并构造 inputsource 对象
- 获取 xml 文件的实体解析器和验证模式
- 加载 xml 文件,获取对应的 document 对象
- 由 document 对象解析并注册 bean
1.利用 encodedresource 二次包装资源文件
采用 org.springframework.core.io.support.encodedresource 对resource 进行二次封装.
2.获取资源输入流,并构造 inputsource 对象
对资源进行编码封装之后,开始真正进入 this.loadbeandefinitions(new encodedresource(resource)) 的过程,该方法源码如下:
publicintloadbeandefinitions(encodedresource encodedresource)throwsbeandefinitionstoreexception{ assert.notnull(encodedresource, "encodedresource must not be null"); if (logger.isinfoenabled()) { logger.info("loading xml bean definitions from " + encodedresource.getresource()); } // 标记正在加载的资源,防止循环引用 set<encodedresource> currentresources = this.resourcescurrentlybeingloaded.get(); if (currentresources == null) { currentresources = new hashset<encodedresource>(4); this.resourcescurrentlybeingloaded.set(currentresources); } if (!currentresources.add(encodedresource)) { throw new beandefinitionstoreexception("detected cyclic loading of " + encodedresource + " - check your import definitions!"); } try { // 获取资源的输入流 inputstream inputstream = encodedresource.getresource().getinputstream(); try { // 构造inputsource对象 inputsource inputsource = new inputsource(inputstream); if (encodedresource.getencoding() != null) { inputsource.setencoding(encodedresource.getencoding()); } // 真正开始从xml文件中加载bean定义 return this.doloadbeandefinitions(inputsource, encodedresource.getresource()); } finally { inputstream.close(); } } catch (ioexception ex) { throw new beandefinitionstoreexception("ioexception parsing xml document from " + encodedresource.getresource(), ex); } finally { currentresources.remove(encodedresource); if (currentresources.isempty()) { this.resourcescurrentlybeingloaded.remove(); } } }
需要知晓的是 org.xml.sax.inputsource
不是 spring 中定义的类,这个类来自 jdk,是 java 对 xml 实体提供的原生支持。这个方法主要还是做了一些准备工作,按照 spring 方法的命名相关,真正干活的方法一般都是以 “do” 开头的,这里的 this.doloadbeandefinitions(inputsource, encodedresource.getresource())
就是真正开始加载 xml 的入口,该方法源码如下:
protectedintdoloadbeandefinitions(inputsource inputsource, resource resource)throwsbeandefinitionstoreexception{ try { // 1. 加载xml文件,获取到对应的document(包含获取xml文件的实体解析器和验证模式) document doc = this.doloaddocument(inputsource, resource); // 2. 解析document对象,并注册bean return this.registerbeandefinitions(doc, resource); } catch (beandefinitionstoreexception ex) { // 这里是连环catch,省略 } }
方面里面的逻辑还是很清晰的,第一步获取 org.w3c.dom.document 对象,第二步由该对象解析得到 beandefinition 对象,并注册到 ioc 容器中。
3.获取 xml 文件的实体解析器和验证模式
this.doloaddocument(inputsource, resource)
包含了获取实体解析器、验证模式,以及 document 对象的逻辑,源码如下:
protecteddocumentdoloaddocument(inputsource inputsource, resource resource)throwsexception{ return this.documentloader.loaddocument( inputsource, this.getentityresolver(), // 获取实体解析器 this.errorhandler, this.getvalidationmodeforresource(resource), // 获取验证模式 this.isnamespaceaware()); }
xml 是半结构化数据,xml 的验证模式用于保证结构的正确性,常见的验证模式有 dtd 和 xsd 两种,获取验证模式的源码如下:
protectedintgetvalidationmodeforresource(resource resource){ int validationmodetouse = this.getvalidationmode(); if (validationmodetouse != validation_auto) { // 手动指定了验证模式 return validationmodetouse; } // 没有指定验证模式,则自动检测 int detectedmode = this.detectvalidationmode(resource); if (detectedmode != validation_auto) { return detectedmode; } // 检测验证模式失败,默认采用xsd验证 return validation_xsd; }
上面源码描述了获取验证模式的执行流程,如果没有手动指定,那么 spring 会去自动检测。对于 xml 文件的解析,sax 首先会读取 xml 文件头声明,以获取对应验证文件地址,并下载对应的文件,如果网络不正常,则会影响下载过程,这个时候可以通过注册一个实体解析来实现寻找验证文件的过程。
4.加载 xml 文件,获取对应的 document 对象
获取对应的验证模式和解析器,解析去就可以加载 document 对象了,这里本质上调用的是 org.springframework.beans.factory.xml.defaultdocumentloader
的 loaddocument() 方法,源码如下:
publicdocumentloaddocument(inputsource inputsource, entityresolver entityresolver, errorhandler errorhandler, int validationmode, boolean namespaceaware) throws exception { documentbuilderfactory factory = this.createdocumentbuilderfactory(validationmode, namespaceaware); if (logger.isdebugenabled()) { logger.debug("using jaxp provider [" + factory.getclass().getname() + "]"); } documentbuilder builder = this.createdocumentbuilder(factory, entityresolver, errorhandler); return builder.parse(inputsource); }
整个过程类似于我们平常解析 xml 文件的流程。
5.由 document 对象解析并注册 bean
完成了对 xml 文件的到 document 对象的解析,我们终于可以解析 document 对象,并注册 bean 了,这一过程发生在 this.registerbeandefinitions(doc, resource)
中,源码如下:
publicintregisterbeandefinitions(document doc, resource resource)throwsbeandefinitionstoreexception{ // 使用defaultbeandefinitiondocumentreader构造 beandefinitiondocumentreader documentreader = this.createbeandefinitiondocumentreader(); // 记录之前已经注册的beandefinition个数 int countbefore = this.getregistry().getbeandefinitioncount(); // 加载并注册bean documentreader.registerbeandefinitions(doc, createreadercontext(resource)); // 返回本次加载的bean的数量 return getregistry().getbeandefinitioncount() - countbefore; }
这里方法的作用是创建对应的 beandefinitiondocumentreader,并计算返回了过程中新注册的 bean 的数量,而具体的注册过程,则是由 beandefinitiondocumentreader 来完成的,具体的实现位于子类 defaultbeandefinitiondocumentreader 中:
publicvoidregisterbeandefinitions(document doc, xmlreadercontext readercontext){ this.readercontext = readercontext; logger.debug("loading bean definitions"); // 获取文档的root结点 element root = doc.getdocumentelement(); this.doregisterbeandefinitions(root); }
还是按照 spring 命名习惯,doregisterbeandefinitions 才是真正干活的地方,这也是真正开始解析配置的核心所在:
protectedvoiddoregisterbeandefinitions(element root){ beandefinitionparserdelegate parent = this.delegate; this.delegate = this.createdelegate(getreadercontext(), root, parent); if (this.delegate.isdefaultnamespace(root)) { // 处理profile标签(其作用类比pom.xml中的profile) string profilespec = root.getattribute(profile_attribute); if (stringutils.hastext(profilespec)) { string[] specifiedprofiles = stringutils.tokenizetostringarray(profilespec, beandefinitionparserdelegate.multi_value_attribute_delimiters); if (!this.getreadercontext().getenvironment().acceptsprofiles(specifiedprofiles)) { if (logger.isinfoenabled()) { logger.info("skipped xml bean definition file due to specified profiles [" + profilespec + "] not matching: " + getreadercontext().getresource()); } return; } } } // 解析预处理,留给子类实现 this.preprocessxml(root); // 解析并注册beandefinition this.parsebeandefinitions(root, this.delegate); // 解析后处理,留给子类实现 this.postprocessxml(root); this.delegate = parent; }
方法中显示处理了 标签,这个属性在 spring 中不是很常用,不过在 maven 的 pom.xml 中则很常见,意义也是相同的,就是配置多套环境,从而在部署的时候可以根据具体环境来选择使用哪一套配置。方法中会先去检测是否配置了 profile,如果配置了就需要从上下文环境中确认当前激活了哪一套 profile。
方法在解析并注册 beandefinition 前后各设置一个模板方法,留给子类扩展实现,而在 this.parsebeandefinitions(root, this.delegate)
中执行解析和注册逻辑:
protectedvoidparsebeandefinitions(element root, beandefinitionparserdelegate delegate){ 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)) { // 解析默认标签 this.parsedefaultelement(ele, delegate); } else { // 解析自定义标签 delegate.parsecustomelement(ele); } } } } else { // 解析自定义标签 delegate.parsecustomelement(root); } }
方法中判断当前标签是默认标签还是自定义标签,并按照不同的策略去解析,这是一个复杂的过程,后面用文章进行针对性讲解,这里不在往下细究。
到这里我们已经完成了静态配置到动态 beandefinition 的解析,这个时候 bean 的定义已经处于内存中,解析去将是探究如何获取并使用 bean 的过程。
二. bean的获取
在完成了 bean 的加载过程之后,我们可以调用 beanfactory.getbean("mybean")
方法来获取目标对象,这里本质上调用的是 org.springframework.beans.factory.support.abstractbeanfactory
的 getbean() 方法,源码如下:
publicobjectgetbean(string name)throwsbeansexception{ return this.dogetbean(name, null, null, false); }
这里调用 this.dogetbean(name, null, null, false)
来实现具体逻辑,也符合我们的预期,该方法可以看做是获取 bean 的整体框架,一个函数完成了整个过程的模块调度,还是挺复杂的:
protected <t> tdogetbean( final string name, final class<t> requiredtype, final object[] args, boolean typecheckonly) throws beansexception { /* * 转化对应的beanname * * 传入的参数可能是alias,也可能是factorybean,所以需要进行解析,主要包含以下内容: * 1. 去除factorybean的修饰符“&” * 2. 取指定alias对应的最终的name */ final string beanname = this.transformedbeanname(name); object bean; /* * 检查缓存或者实例工厂中是否有对应的实例 * * 为什么会一开始就进行检查? * 因为在创建单例bean的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖 * spring创建bean的原则是不等bean创建完成就会将创建bean的objectfactory提前曝光,即将对应的objectfactory加入到缓存 * 一旦下一个bean创建需要依赖上一个bean,则直接使用objectfactory */ object sharedinstance = this.getsingleton(beanname); // 获取单例 if (sharedinstance != null && args == null) { // 实例已经存在 if (logger.isdebugenabled()) { if (this.issingletoncurrentlyincreation(beanname)) { logger.debug("returning eagerly cached instance of singleton bean '" + beanname + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.debug("returning cached instance of singleton bean '" + beanname + "'"); } } // 返回对应的实例 bean = this.getobjectforbeaninstance(sharedinstance, name, beanname, null); } else { // 单例实例不存在 if (this.isprototypecurrentlyincreation(beanname)) { /* * 只有在单例模式下才会尝试解决循环依赖问题 * 对于原型模式,如果存在循环依赖,也就是满足this.isprototypecurrentlyincreation(beanname),抛出异常 */ throw new beancurrentlyincreationexception(beanname); } beanfactory parentbeanfactory = this.getparentbeanfactory(); if (parentbeanfactory != null && !this.containsbeandefinition(beanname)) { // 如果在beandefinitionmap中(即所有已经加载的类中)不包含目标bean,则尝试从parentbeanfactory中检测 string nametolookup = this.originalbeanname(name); if (args != null) { // 递归到beanfactory中寻找 return (t) parentbeanfactory.getbean(nametolookup, args); } else { return parentbeanfactory.getbean(nametolookup, requiredtype); } } // 如果不仅仅是做类型检查,则创建bean if (!typecheckonly) { this.markbeanascreated(beanname); } try { /* * 将存储xml配置的genericbeandefinition转换成rootbeandefinition * 如果指定了beanname是子bean的话,同时会合并父类的相关属性 */ final rootbeandefinition mbd = this.getmergedlocalbeandefinition(beanname); this.checkmergedbeandefinition(mbd, beanname, args); // 获取当前bean依赖的bean string[] dependson = mbd.getdependson(); if (dependson != null) { // 存在依赖,递归实例化依赖的bean for (string dep : dependson) { if (this.isdependent(beanname, dep)) { throw new beancreationexception(mbd.getresourcedescription(), beanname, "circular depends-on relationship between '" + beanname + "' and '" + dep + "'"); } // 缓存依赖调用 this.registerdependentbean(dep, beanname); this.getbean(dep); } } // 实例化依赖的bean后,实例化mbd自身 if (mbd.issingleton()) { // scope == singleton sharedinstance = this.getsingleton(beanname, new objectfactory<object>() { @override publicobjectgetobject()throwsbeansexception{ try { return createbean(beanname, mbd, args); } catch (beansexception ex) { // explicitly remove instance from singleton cache: it might have been put there // eagerly by the creation process, to allow for circular reference resolution. // also remove any beans that received a temporary reference to the bean. destroysingleton(beanname); throw ex; } } }); bean = this.getobjectforbeaninstance(sharedinstance, name, beanname, mbd); } else if (mbd.isprototype()) { // scope == prototype object prototypeinstance; try { this.beforeprototypecreation(beanname); prototypeinstance = this.createbean(beanname, mbd, args); } finally { this.afterprototypecreation(beanname); } // 返回对应的实例 bean = this.getobjectforbeaninstance(prototypeinstance, name, beanname, mbd); } else { // 其它scope string scopename = mbd.getscope(); final scope scope = this.scopes.get(scopename); if (scope == null) { throw new illegalstateexception("no scope registered for scope name '" + scopename + "'"); } try { object scopedinstance = scope.get(beanname, new objectfactory<object>() { @override publicobjectgetobject()throwsbeansexception{ beforeprototypecreation(beanname); try { return createbean(beanname, mbd, args); } finally { afterprototypecreation(beanname); } } }); // 返回对应的实例 bean = this.getobjectforbeaninstance(scopedinstance, name, beanname, mbd); } catch (illegalstateexception ex) { throw new beancreationexception(beanname, "scope '" + scopename + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (beansexception ex) { cleanupafterbeancreationfailure(beanname); throw ex; } } // 检查需要的类型是否符合bean的实际类型,对应getbean时指定的requiretype if (requiredtype != null && bean != null && !requiredtype.isassignablefrom(bean.getclass())) { try { return this.gettypeconverter().convertifnecessary(bean, requiredtype); } catch (typemismatchexception ex) { if (logger.isdebugenabled()) { logger.debug("failed to convert bean '" + name + "' to required type '" + classutils.getqualifiedname(requiredtype) + "'", ex); } throw new beannotofrequiredtypeexception(name, requiredtype, bean.getclass()); } } return (t) bean; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。