Spring源码剖析2:Spring IOC容器的加载过程
spring ioc 容器的加载流程
1.目标:熟练使用spring,并分析其源码,了解其中的思想。这篇主要介绍spring ioc 容器的加载
2.前提条件:会使用debug
3.源码分析方法:intellj idea debug 模式下源码追溯
通过classpathxmlapplicationcontext 进行xml 件的读取,从每个堆栈中读取程序的运行信息
4.注意:由于spring的类继承体系比较复杂,不能全部贴图,所以只将分析源码之后发现的最主要的类继承结构类图贴在下方。
5.关于spring ioc
demo:我们从demo入手一步步进行代码追溯。
spring ioc demo
1.定义数据访问接口iuserdao.java
public interface iuserdao { public void insertuser(string username,string password); }
2.定义iuserdao.java实现类iuserdaoimpl.java
public class userdaoimpl implements iuserdao { @override public void insertuser(string username, string password) { system.out.println("----userdaoimpl --adduser----"); } }
3.定义业务逻辑接口userservice.java
public interface userservice { public void adduser(string username,string password); }
4.定义userservice.java实现类userserviceimpl.java
public class userserviceimpl implements userservice { private iuserdao userdao; //set方法 public void setuserdao(iuserdao userdao) { this.userdao = userdao; } @override public void adduser(string username,string password) { userdao.insertuser(username,password); } }
bean.xml配置文件
<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-3.0.xsd "> <!--id名字自己取,class表示他代表的类,如果在包里的话需要加上包名--> <bean id="userservice" class="userserviceimpl" > <!--property代表是通过set方法注入,ref的值表示注入的内容--> <property name="userdao" ref="userdao"/> </bean> <bean id="userdao" class="userdaoimpl"/> </beans>
applicationcontext 继承结构
1.顶层接口:applicationcontext
2.classpathxmlapplicationcontext实现类继承abstractxmlapplication 抽象类
3.abstractxmlapplication 继承abstractrefreshableconfigapplicationcontext
4.abstractrefreshableconfigapplicationcontext抽象类继承abstractrefreshableapplicationcontext
5.abstractrefreshableapplicationcontext 继承 abstractapplicationcontext
6.abstractapplicationcontext 实现configurableapplicationcontext 接口
7.configurableapplicationcontext 接口继承
applicationcontext接口
总体来说继承实现结构较深,内部使用了大量适配器模式。
以classpathxmlapplicationcontext为例,继承类图如下图所示:
spring ioc容器加载过程源码详解
在开始之前,先介绍一个整体的概念。即spring ioc容器的加载,大体上经过以下几个过程:
资源文件定位、解析、注册、实例化
1.资源文件定位
其中资源文件定位,一般是在applicationcontext的实现类里完成的,因为applicationcontext接口继承resourcepatternresolver 接口,resourcepatternresolver接口继承resourceloader接口,resourceloader其中的getresource()方法,可以将外部的资源,读取为resource类。
2.解析defaultbeandefinitiondocumentreader,
解析主要是在beandefinitionreader中完成的,最常用的实现类是xmlbeandefinitionreader,其中的loadbeandefinitions()方法,负责读取resource,并完成后续的步骤。applicationcontext完成资源文件定位之后,是将解析工作委托给xmlbeandefinitionreader来完成的
解析这里涉及到很多步骤,最常见的情况,资源文件来自一个xml配置文件。首先是beandefinitionreader,将xml文件读取成w3c的document文档。
defaultbeandefinitiondocumentreader对document进行进一步解析。然后defaultbeandefinitiondocumentreader又委托给beandefinitionparserdelegate进行解析。如果是标准的xml namespace元素,会在delegate内部完成解析,如果是非标准的xml namespace元素,则会委托合适的namespacehandler进行解析最终解析的结果都封装为beandefinitionholder,至此解析就算完成。
后续会进行细致讲解。
3.注册
然后bean的注册是在beanfactory里完成的,beanfactory接口最常见的一个实现类是defaultlistablebeanfactory,它实现了beandefinitionregistry接口,所以其中的registerbeandefinition()方法,可以对beandefinition进行注册这里附带一提,最常见的xmlwebapplicationcontext不是自己持有beandefinition的,它继承自abstractrefreshableapplicationcontext,其持有一个defaultlistablebeanfactory的字段,就是用它来保存beandefinition
所谓的注册,其实就是将beandefinition的name和实例,保存到一个map中。刚才说到,最常用的实现defaultlistablebeanfactory,其中的字段就是beandefinitionmap,是一个concurrenthashmap。
代码如下:
>1.defaultlistablebeanfactory继承实现关系
public class defaultlistablebeanfactory extends abstractautowirecapablebeanfactory implements configurablelistablebeanfactory, beandefinitionregistry, serializable { // defaultlistablebeanfactory的实例中最终保存了所有注册的bean beandefinitionmap /** map of bean definition objects, keyed by bean name */ private final map<string, beandefinition> beandefinitionmap = new concurrenthashmap<string, beandefinition>(64); //实现beandefinitionregistry中定义的registerbeandefinition()抽象方法 public void registerbeandefinition(string beanname, beandefinition beandefinition) throws beandefinitionstoreexception { }
>2.beandefinitionregistry接口
public interface beandefinitionregistry extends aliasregistry { //定义注册beandefinition实例的抽象方法 void registerbeandefinition(string beanname, beandefinition beandefinition) throws beandefinitionstoreexception;
4.实例化
注册也完成之后,在beanfactory的getbean()方法之中,会完成初始化,也就是依赖注入的过程
大体上的流程就是这样。
refresh()方法
1.目标:
这篇记录debug 追溯源码的过程,大概分三个篇幅,这是第一篇,现整体了解一下运行流程,定位资源加载,资源解析,bean 注册发生的位置。
2.记录结构:
1.调试栈截图
2.整体流程
3.bean.xml的处理
每段代码下面有相应的讲解
调试栈截图
每个栈帧中方法的行号都有标明,按照行号追溯源码,然后配合教程能够快速学习。
整体流程
ioc容器实例化代码
applicationcontext applicationcontext = new classpathxmlapplicationcontext("bean.xml");
进入代码中一步步追溯,发现重要方法:refresh();
如下所示:
public void refresh() throws beansexception, illegalstateexception { synchronized (this.startupshutdownmonitor) { // prepare this context for refreshing. preparerefresh(); //beanfactory实例化方法 单步调试入口 // tell the subclass to refresh the internal bean factory. configurablelistablebeanfactory beanfactory = obtainfreshbeanfactory(); // prepare the bean factory for use in this context. preparebeanfactory(beanfactory); try { // allows post-processing of the bean factory in context subclasses. postprocessbeanfactory(beanfactory); // invoke factory processors registered as beans in the context. invokebeanfactorypostprocessors(beanfactory); // register bean processors that intercept bean creation. registerbeanpostprocessors(beanfactory); // initialize message source for this context. initmessagesource(); // initialize event multicaster for this context. initapplicationeventmulticaster(); // initialize other special beans in specific context subclasses. onrefresh(); // check for listener beans and register them. registerlisteners(); // instantiate all remaining (non-lazy-init) singletons. finishbeanfactoryinitialization(beanfactory); // last step: publish corresponding event. finishrefresh(); } catch (beansexception ex) { // destroy already created singletons to avoid dangling resources. destroybeans(); // reset 'active' flag. cancelrefresh(ex); // propagate exception to caller. throw ex; } } }
首先这个方法是同步的,以避免重复刷新。然后刷新的每个步骤,都放在单独的方法里,比较清晰,可以按顺序一个个看
首先是preparerefresh()方法
protected void preparerefresh() { this.startupdate = system.currenttimemillis(); synchronized (this.activemonitor) { this.active = true; } if (logger.isinfoenabled()) { logger.info("refreshing " + this); } // initialize any placeholder property sources in the context environment initpropertysources(); // validate that all properties marked as required are resolvable // see configurablepropertyresolver#setrequiredproperties this.environment.validaterequiredproperties(); }
这个方法里做的事情不多,记录了开始时间,输出日志,另外initpropertysources()方法和validaterequiredproperties()方法一般都没有做什么事。
然后是核心的obtainfreshbeanfactory()方法,这个方法是初始化beanfactory,是整个refresh()方法的核心,其中完成了配置文件的加载、解析、注册,后面会专门详细说 。
这里要说明一下,applicationcontext实现了beanfactory接口,并实现了resourceloader、messagesource等接口,可以认为是增强的beanfactory。但是applicationcontext并不自己重复实现beanfactory定义的方法,而是委托给defaultlistablebeanfactory来实现。这种设计思路也是值得学习的。
后面的 preparebeanfactory()、postprocessbeanfactory()、invokebeanfactorypostprocessors()、registerbeanpostprocessors()、initmessagesource()、initapplicationeventmulticaster()、onrefresh()、registerlisteners()、finishbeanfactoryinitialization()、finishrefresh()等方法,是添加一些后处理器、广播、拦截器等,就不一个个细说了
其中的关键方法是finishbeanfactoryinitialization(),在这个方法中,会对刚才注册的bean(不延迟加载的),进行实例化,所以也是一个核心方法。
bean.xml的处理
从整体上介绍完了流程,接下来就重点看obtainfreshbeanfactory()方法,上文说到,在这个方法里,完成了配置文件的加载、解析、注册
protected configurablelistablebeanfactory obtainfreshbeanfactory() { refreshbeanfactory(); configurablelistablebeanfactory beanfactory = getbeanfactory(); if (logger.isdebugenabled()) { logger.debug("bean factory for " + getdisplayname() + ": " + beanfactory); } return beanfactory; }
这个方法做了2件事,首先通过refreshbeanfactory()方法,创建了defaultlistablebeanfactory的实例,并进行初始化。
protected final void refreshbeanfactory() throws beansexception { if (hasbeanfactory()) { destroybeans(); closebeanfactory(); } try { defaultlistablebeanfactory beanfactory = createbeanfactory(); beanfactory.setserializationid(getid()); customizebeanfactory(beanfactory); loadbeandefinitions(beanfactory); synchronized (this.beanfactorymonitor) { this.beanfactory = beanfactory; } } catch (ioexception ex) { throw new applicationcontextexception("i/o error parsing bean definition source for " + getdisplayname(), ex); } }
首先如果已经有beanfactory实例,就先清空。然后通过createbeanfactory()方法,创建一个defaultlistablebeanfactory的实例
protected defaultlistablebeanfactory createbeanfactory() { return new defaultlistablebeanfactory(getinternalparentbeanfactory()); }
接下来设置id唯一标识
beanfactory.setserializationid(getid());
然后允许用户进行一些自定义的配置
protected void customizebeanfactory(defaultlistablebeanfactory beanfactory) { if (this.allowbeandefinitionoverriding != null) { beanfactory.setallowbeandefinitionoverriding(this.allowbeandefinitionoverriding); } if (this.allowcircularreferences != null) { beanfactory.setallowcircularreferences(this.allowcircularreferences); } beanfactory.setautowirecandidateresolver(new qualifierannotationautowirecandidateresolver()); }
最后,就是核心的loadbeandefinitions()方法
protected void loadbeandefinitions(defaultlistablebeanfactory beanfactory) throws beansexception, ioexception { // create a new xmlbeandefinitionreader for the given beanfactory. xmlbeandefinitionreader beandefinitionreader = new xmlbeandefinitionreader(beanfactory); // configure the bean definition reader with this context's // resource loading environment. beandefinitionreader.setenvironment(this.getenvironment()); beandefinitionreader.setresourceloader(this); beandefinitionreader.setentityresolver(new resourceentityresolver(this)); // allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initbeandefinitionreader(beandefinitionreader); loadbeandefinitions(beandefinitionreader); }
这里首先会创建一个xmlbeandefinitionreader的实例,然后进行初始化。这个xmlbeandefinitionreader中其实传递的beandefinitionregistry类型的实例,为什么可以传递一个beanfactory呢,因为defaultlistablebeanfactory实现了beandefinitionregistry接口,这里是多态的使用。
protected void loadbeandefinitions(defaultlistablebeanfactory beanfactory) throws beansexception, ioexception { // create a new xmlbeandefinitionreader for the given beanfactory. xmlbeandefinitionreader beandefinitionreader = new xmlbeandefinitionreader(beanfactory); // configure the bean definition reader with this context's // resource loading environment. beandefinitionreader.setenvironment(this.getenvironment()); beandefinitionreader.setresourceloader(this); beandefinitionreader.setentityresolver(new resourceentityresolver(this)); // allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initbeandefinitionreader(beandefinitionreader); }
这里要说明一下,applicationcontext并不自己负责配置文件的加载、解析、注册,而是将这些工作委托给xmlbeandefinitionreader来做。
loadbeandefinitions(beandefinitionreader);
这行代码,就是bean定义读取实际发生的地方。这里的工作,主要是xmlbeandefinitionreader来完成的,下一篇博客会详细介绍这个过程。
loadbeandefinitions
loadbeandefinitions: 源码阅读
入口是loadbeandefinitions方法
protected void loadbeandefinitions(xmlbeandefinitionreader reader) throws ioexception { string[] configlocations = getconfiglocations(); if (configlocations != null) { for (string configlocation : configlocations) { reader.loadbeandefinitions(configlocation); } } }
这是解析过程最外围的代码,首先要获取到配置文件的路径,这在之前已经完成了。
然后将每个配置文件的路径,作为参数传给beandefinitionreader的loadbeandefinitions方法里
public int loadbeandefinitions(string location) throws beandefinitionstoreexception { return loadbeandefinitions(location, null); }
这个方法又调用了重载方法
public int loadbeandefinitions(string location, 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; } }
首先getresourceloader()的实现的前提条件是因为xmlbeandefinitionreader在实例化的时候已经确定了创建了实例resourceloader实例, 代码位于 abstractbeandefinitionreader
protected abstractbeandefinitionreader(beandefinitionregistry registry) { assert.notnull(registry, "beandefinitionregistry must not be null"); this.registry = registry; // determine resourceloader to use. if (this.registry instanceof resourceloader) { this.resourceloader = (resourceloader) this.registry; } else { this.resourceloader = new pathmatchingresourcepatternresolver(); } // inherit environment if possible if (this.registry instanceof environmentcapable) { this.environment = ((environmentcapable)this.registry).getenvironment(); } else { this.environment = new standardenvironment(); } }
这个方法比较长,beandefinitionreader不能直接加载配置文件,需要把配置文件封装成resource,然后才能调用重载方法loadbeandefinitions()。所以这个方法其实就是2段,第一部分是委托resourceloader将配置文件封装成resource,第二部分是调用loadbeandefinitions(),对resource进行解析
而这里的resourceloader,就是前面的xmlwebapplicationcontext,因为applicationcontext接口,是继承自resourceloader接口的
resource也是一个接口体系,在web环境下,这里就是servletcontextresource
接下来进入重载方法loadbeandefinitions()
public int loadbeandefinitions(resource... resources) throws beandefinitionstoreexception { assert.notnull(resources, "resource array must not be null"); int counter = 0; for (resource resource : resources) { counter += loadbeandefinitions(resource); } return counter; }
这里就不用说了,就是把每一个resource作为参数,继续调用重载方法。读spring源码,会发现重载方法特别多。
public int loadbeandefinitions(resource resource) throws beandefinitionstoreexception { return loadbeandefinitions(new encodedresource(resource)); }
还是重载方法,不过这里对传进来的resource又进行了一次封装,变成了编码后的resource。
public int loadbeandefinitions(encodedresource encodedresource) throws beandefinitionstoreexception { 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 = new inputsource(inputstream); if (encodedresource.getencoding() != null) { inputsource.setencoding(encodedresource.getencoding()); } return 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(); } } }
这个就是loadbeandefinitions()的最后一个重载方法,比较长,可以拆看来看。
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!"); }
这第一部分,是处理线程相关的工作,把当前正在解析的resource,设置为当前resource。
try { inputstream inputstream = encodedresource.getresource().getinputstream(); try { inputsource inputsource = new inputsource(inputstream); if (encodedresource.getencoding() != null) { inputsource.setencoding(encodedresource.getencoding()); } return doloadbeandefinitions(inputsource, encodedresource.getresource()); } finally { inputstream.close(); } }
这里是第二部分,是核心,首先把resource还原为inputstream,然后调用实际解析的方法doloadbeandefinitions()。可以看到,这种命名方式是很值得学习的,一种业务方法,比如parse(),可能需要做一些外围的工作,然后实际解析的方法,可以命名为doparse()。这种doxxx()的命名方法,在很多开源框架中都有应用,比如logback等。
接下来就看一下这个doloadbeandefinitions()方法
protected int doloadbeandefinitions(inputsource inputsource, resource resource) throws beandefinitionstoreexception { try { document doc = doloaddocument(inputsource, resource);return registerbeandefinitions(doc, resource); return registerbeandefinitions(doc, resource); } catch (beandefinitionstoreexception ex) { throw ex; } catch (saxparseexception ex) { throw new xmlbeandefinitionstoreexception(resource.getdescription(), "line " + ex.getlinenumber() + " in xml document from " + resource + " is invalid", ex); } catch (saxexception ex) { throw new xmlbeandefinitionstoreexception(resource.getdescription(), "xml document from " + resource + " is invalid", ex); } catch (parserconfigurationexception ex) { throw new beandefinitionstoreexception(resource.getdescription(), "parser configuration exception parsing xml from " + resource, ex); } catch (ioexception ex) { throw new beandefinitionstoreexception(resource.getdescription(), "ioexception parsing xml document from " + resource, ex); } catch (throwable ex) { throw new beandefinitionstoreexception(resource.getdescription(), "unexpected exception parsing xml document from " + resource, ex); } }
抛开异常处理:核心代码如下:
document doc = doloaddocument(inputsource, resource); return registerbeandefinitions(doc, resource);
doloaddocument方法将inputstream读取成标准的document对象,然后调用registerbeandefinitions(),进行解析工作。
protected document doloaddocument(inputsource inputsource, resource resource) throws exception { return this.documentloader.loaddocument(inputsource, getentityresolver(), this.errorhandler, getvalidationmodeforresource(resource), isnamespaceaware()); }
接下来就看一下这个核心方法registerbeandefinitions
public int registerbeandefinitions(document doc, resource resource) throws beandefinitionstoreexception { //创建的其实是defaultbeandefinitiondocumentreader 的实例,利用反射创建的。 beandefinitiondocumentreader documentreader = createbeandefinitiondocumentreader(); documentreader.setenvironment(this.getenvironment()); int countbefore = getregistry().getbeandefinitioncount(); documentreader.registerbeandefinitions(doc, createreadercontext(resource)); return getregistry().getbeandefinitioncount() - countbefore; }
这里注意两点 :
1.document对象
首先这个document对象,是w3c定义的标准xml对象,跟spring无关。其次这个registerbeandefinitions方法,我觉得命名有点误导性。因为这个时候实际上解析还没有开始,怎么直接就注册了呢。比较好的命名,我觉得可以是parseandregisterbeandefinitions()。
2.documentreader的创建时使用反射创建的,代码如下
protected beandefinitiondocumentreader createbeandefinitiondocumentreader() { return beandefinitiondocumentreader.class.cast(beanutils. instantiateclass(this.documentreaderclass)); }
instantiateclass方法中传入了一个class类型的参数。追溯发现下述代码:
private class<?> documentreaderclass = defaultbeandefinitiondocumentreader.class;
所以创建的documentreaderclass是defaultbeandefinitiondocumentreader类的实例。
接下来就进入beandefinitiondocumentreader 中定义的registerbeandefinitions()方法看看
public void registerbeandefinitions(document doc, xmlreadercontext readercontext) { this.readercontext = readercontext; logger.debug("loading bean definitions"); element root = doc.getdocumentelement(); doregisterbeandefinitions(root); }
处理完外围事务之后,进入doregisterbeandefinitions()方法,这种命名规范,上文已经介绍过了
protected void doregisterbeandefinitions(element root) { string profilespec = root.getattribute(profile_attribute); if (stringutils.hastext(profilespec)) { assert.state(this.environment != null, "environment property must not be null"); string[] specifiedprofiles = stringutils.tokenizetostringarray(profilespec, beandefinitionparserdelegate.multi_value_attribute_delimiters); if (!this.environment.acceptsprofiles(specifiedprofiles)) { return; } } // any nested <beans> elements will cause recursion in this method. in // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. beandefinitionparserdelegate parent = this.delegate; this.delegate = createhelper(readercontext, root, parent); preprocessxml(root); parsebeandefinitions(root, this.delegate); postprocessxml(root); this.delegate = parent; }
这个方法也比较长,拆开来看
string profilespec = root.getattribute(profile_attribute); if (stringutils.hastext(profilespec)) { assert.state(this.environment != null, "environment property must not be null"); string[] specifiedprofiles = stringutils.tokenizetostringarray(profilespec, beandefinitionparserdelegate.multi_value_attribute_delimiters); if (!this.environment.acceptsprofiles(specifiedprofiles)) { return; } }
如果配置文件中元素,配有profile属性,就会进入这一段,不过一般都是不会的
beandefinitionparserdelegate parent = this.delegate; this.delegate = createhelper(readercontext, root, parent); preprocessxml(root); parsebeandefinitions(root, this.delegate); postprocessxml(root); this.delegate = parent;
然后这里创建了beandefinitionparserdelegate对象,preprocessxml()和postprocessxml()都是空方法,核心就是parsebeandefinitions()方法。这里又把beandefinition解析和注册的工作,委托给了beandefinitionparserdelegate对象,在parsebeandefinitions()方法中完成
总的来说,解析工作的委托链是这样的:classpathxmlapplicationcontext,xmlbeandefinitionreader,defaultbeandefinitiondocumentreader,beandefinitionparserdelegate
classpathxmlapplicationcontext作为最外围的组件,发起解析的请求
xmlbeandefinitionreader将配置文件路径封装为resource,读取出w3c定义的document对象,然后委托给defaultbeandefinitiondocumentreader
defaultbeandefinitiondocumentreader就开始做实际的解析工作了,但是涉及到bean的具体解析,它还是会继续委托给beandefinitionparserdelegate来做。
接下来在parsebeandefinitions()方法中发生了什么,以及beandefinitionparserdelegate类完成的工作,在下一篇博客中继续介绍。
loadbeandefinitions
beandefinition的解析,已经走到了defaultbeandefinitiondocumentr
eader里,这时候配置文件已经被加载,并解析成w3c的document对象。这篇博客就接着介绍,defaultbeandefinitiondocumentreader和beandefinitionparserdelegate类,是怎么协同完成bean的解析和注册的。
beandefinitionparserdelegate parent = this.delegate; this.delegate = createhelper(readercontext, root, parent); preprocessxml(root); parsebeandefinitions(root, this.delegate); postprocessxml(root); this.delegate = parent;
这段代码,创建了一个beandefinitionparserdelegate组件,然后就是preprocessxml()、parsebeandefinitions()、postprocessxml()方法
其中preprocessxml()和postprocessxml()默认是空方法,接下来就看下parsebeandefinitions()方法
protected void parsebeandefinitions(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)) { parsedefaultelement(ele, delegate); } else { delegate.parsecustomelement(ele); } } } } else { delegate.parsecustomelement(root); } }
从这个方法开始,beandefinitionparserdelegate就开始发挥作用了,判断当前解析元素是否属于默认的命名空间,如果是的话,就调用parsedefaultelement()方法,否则调用delegate上parsecustomelement()方法
public boolean isdefaultnamespace(string namespaceuri) { return (!stringutils.haslength(namespaceuri) || beans_namespace_uri.equals(namespaceuri)); } public boolean isdefaultnamespace(node node) { return isdefaultnamespace(getnamespaceuri(node)); }
只有,会被认为是默认的命名空间。也就是说,beans、bean这些元素,会认为属于默认的命名空间,而像task:scheduled这些,就认为不属于默认命名空间。
根节点beans的一个子节点bean,是属于默认命名空间的,所以会进入parsedefaultelement()方法
private void parsedefaultelement(element ele, beandefinitionparserdelegate delegate) { if (delegate.nodenameequals(ele, import_element)) { importbeandefinitionresource(ele); } else if (delegate.nodenameequals(ele, alias_element)) { processaliasregistration(ele); } else if (delegate.nodenameequals(ele, bean_element)) { processbeandefinition(ele, delegate); } else if (delegate.nodenameequals(ele, nested_beans_element)) { // recurse doregisterbeandefinitions(ele); } }
这里可能会有4种情况,import、alias、bean、beans,分别有一个方法与之对应,这里解析的是bean元素,所以会进入processbeandefinition()方法
protected void processbeandefinition(element ele, beandefinitionparserdelegate delegate) { beandefinitionholder bdholder = delegate.parsebeandefinitionelement(ele); if (bdholder != null) { bdholder = delegate.decoratebeandefinitionifrequired(ele, bdholder); try { // register the final decorated instance. beandefinitionreaderutils.registerbeandefinition(bdholder, getreadercontext().getregistry()); } catch (beandefinitionstoreexception ex) { getreadercontext().error("failed to register bean definition with name '" + bdholder.getbeanname() + "'", ele, ex); } // send registration event. getreadercontext().firecomponentregistered(new beancomponentdefinition(bdholder)); } }
这里主要有3个步骤,先是委托delegate对bean进行解析,然后委托delegate对bean进行装饰,最后由一个工具类来完成beandefinition的注册
可以看出来,defaultbeandefinitiondocumentreader不负责任何具体的bean解析,它面向的是xml document对象,根据其元素的命名空间和名称,起一个类似路由的作用(不过,命名空间的判断,也是委托给delegate来做的)。所以这个类的命名,是比较贴切的,突出了其面向document的特性。具体的工作,是由beandefinitionparserdelegate来完成的
下面就看下parsebeandefinitionelement()方法
public beandefinitionholder parsebeandefinitionelement(element ele, beandefinition containingbean) { string id = ele.getattribute(id_attribute); string nameattr = ele.getattribute(name_attribute); list<string> aliases = new arraylist<string>(); if (stringutils.haslength(nameattr)) { string[] namearr = stringutils.tokenizetostringarray(nameattr, multi_value_attribute_delimiters); aliases.addall(arrays.aslist(namearr)); } string beanname = id; if (!stringutils.hastext(beanname) && !aliases.isempty()) { beanname = aliases.remove(0); if (logger.isdebugenabled()) { logger.debug("no xml 'id' specified - using '" + beanname + "' as bean name and " + aliases + " as aliases"); } } if (containingbean == null) { checknameuniqueness(beanname, aliases, ele); } abstractbeandefinition beandefinition = parsebeandefinitionelement(ele, beanname, containingbean); if (beandefinition != null) { if (!stringutils.hastext(beanname)) { try { if (containingbean != null) { beanname = beandefinitionreaderutils.generatebeanname( beandefinition, this.readercontext.getregistry(), true); } else { beanname = this.readercontext.generatebeanname(beandefinition); // register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // this is expected for spring 1.2/2.0 backwards compatibility. string beanclassname = beandefinition.getbeanclassname(); if (beanclassname != null && beanname.startswith(beanclassname) && beanname.length() > beanclassname.length() && !this.readercontext.getregistry().isbeannameinuse(beanclassname)) { aliases.add(beanclassname); } } if (logger.isdebugenabled()) { logger.debug("neither xml 'id' nor 'name' specified - " + "using generated bean name [" + beanname + "]"); } } catch (exception ex) { error(ex.getmessage(), ele); return null; } } string[] aliasesarray = stringutils.tostringarray(aliases); return new beandefinitionholder(beandefinition, beanname, aliasesarray); } return null; }
这个方法很长,可以分成三段来看
string id = ele.getattribute(id_attribute); string nameattr = ele.getattribute(name_attribute); list<string> aliases = new arraylist<string>(); if (stringutils.haslength(nameattr)) { string[] namearr = stringutils.tokenizetostringarray(nameattr, multi_value_attribute_delimiters); aliases.addall(arrays.aslist(namearr)); } string beanname = id; if (!stringutils.hastext(beanname) && !aliases.isempty()) { beanname = aliases.remove(0); if (logger.isdebugenabled()) { logger.debug("no xml 'id' specified - using '" + beanname + "' as bean name and " + aliases + " as aliases"); } } if (containingbean == null) { checknameuniqueness(beanname, aliases, ele); }
这一段,主要是处理一些跟alias,id等标识相关的东西
abstractbeandefinition beandefinition = parsebeandefinitionelement(ele, beanname, containingbean);
这一行是核心,进行实际的解析
if (beandefinition != null) { if (!stringutils.hastext(beanname)) { try { if (containingbean != null) { beanname = beandefinitionreaderutils.generatebeanname( beandefinition, this.readercontext.getregistry(), true); } else { beanname = this.readercontext.generatebeanname(beandefinition); // register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // this is expected for spring 1.2/2.0 backwards compatibility. string beanclassname = beandefinition.getbeanclassname(); if (beanclassname != null && beanname.startswith(beanclassname) && beanname.length() > beanclassname.length() && !this.readercontext.getregistry().isbeannameinuse(beanclassname)) { aliases.add(beanclassname); } } if (logger.isdebugenabled()) { logger.debug("neither xml 'id' nor 'name' specified - " + "using generated bean name [" + beanname + "]"); } } catch (exception ex) { error(ex.getmessage(), ele); return null; } } string[] aliasesarray = stringutils.tostringarray(aliases); return new beandefinitionholder(beandefinition, beanname, aliasesarray); }
这段是后置处理,对beanname进行处理
前置处理和后置处理,不是核心,就不细看了,重点看下核心的那一行调用
public abstractbeandefinition parsebeandefinitionelement( element ele, string beanname, beandefinition containingbean) { this.parsestate.push(new beanentry(beanname)); string classname = null; if (ele.hasattribute(class_attribute)) { classname = ele.getattribute(class_attribute).trim(); } try { string parent = null; if (ele.hasattribute(parent_attribute)) { parent = ele.getattribute(parent_attribute); } abstractbeandefinition bd = createbeandefinition(classname, parent); parsebeandefinitionattributes(ele, beanname, containingbean, bd); bd.setdescription(domutils.getchildelementvaluebytagname(ele, description_element)); parsemetaelements(ele, bd); parselookupoverridesubelements(ele, bd.getmethodoverrides()); parsereplacedmethodsubelements(ele, bd.getmethodoverrides()); parseconstructorargelements(ele, bd); parsepropertyelements(ele, bd); parsequalifierelements(ele, bd); bd.setresource(this.readercontext.getresource()); bd.setsource(extractsource(ele)); return bd; } catch (classnotfoundexception ex) { error("bean class [" + classname + "] not found", ele, ex); } catch (noclassdeffounderror err) { error("class that bean class [" + classname + "] depends on not found", ele, err); } catch (throwable ex) { error("unexpected failure during bean definition parsing", ele, ex); } finally { this.parsestate.pop(); } return null; }
这个方法也挺长的,拆开看看
this.parsestate.push(new beanentry(beanname)); string classname = null; if (ele.hasattribute(class_attribute)) { classname = ele.getattribute(class_attribute).trim(); }
这段是从配置中抽取出类名。接下来的长长一段,把异常处理先抛开,看看实际的业务
string parent = null; if (ele.hasattribute(parent_attribute)) { parent = ele.getattribute(parent_attribute); } abstractbeandefinition bd = createbeandefinition(classname, parent); parsebeandefinitionattributes(ele, beanname, containingbean, bd); bd.setdescription(domutils.getchildelementvaluebytagname(ele, description_element)); parsemetaelements(ele, bd); parselookupoverridesubelements(ele, bd.getmethodoverrides()); parsereplacedmethodsubelements(ele, bd.getmethodoverrides()); parseconstructorargelements(ele, bd); parsepropertyelements(ele, bd); parsequalifierelements(ele, bd); bd.setresource(this.readercontext.getresource()); bd.setsource(extractsource(ele)); return bd;
这里每个方法的命名,就说明了是要干什么,可以一个个跟进去看,本文就不细说了。总之,经过这里的解析,就得到了一个完整的beandefinitionholder。只是说明一下,如果在配置文件里,没有对一些属性进行设置,比如autowire-candidate等,那么这个解析生成的beandefinition,都会得到一个默认值
然后,对这个bean做一些必要的装饰
public beandefinitionholder decoratebeandefinitionifrequired( element ele, beandefinitionholder definitionholder, 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; }
持续单步调试,代码继续运行到defaultbeandefinitiondocumentreader中的processbeandefinition中的registerbeandefinition()
beandefinitionreaderutils.registerbeandefinition(bdholder, getreadercontext().getregistry());
单步进入代码发现beandefinitionreaderutils静态方法registerbeandefinition()
public static void registerbeandefinition( beandefinitionholder definitionholder, beandefinitionregistry registry) throws beandefinitionstoreexception { // register bean definition under primary name. string beanname = definitionholder.getbeanname(); // 其实调用的是defaultlistablebeanfactory中的registerbeandefinition方法 registry.registerbeandefinition(beanname, definitionholder.getbeandefinition()); // register aliases for bean name, if any. string[] aliases = definitionholder.getaliases(); if (aliases != null) { for (string aliase : aliases) { registry.registeralias(beanname, aliase); } } }
解释一下其实调用的是defaultlistablebeanfactory中的registerbeandefinition方法这句话,因为defaultlistablebeanfactory实现beandefinitionregistry接口,beandefinitionregistry接口中定义了registerbeandefinition()方法
看下defaultlistablebeanfactory中registerbeandefinition()实例方法的具体实现:
public void registerbeandefinition(string beanname, beandefinition beandefinition) throws beandefinitionstoreexception { assert.hastext(beanname, "bean name must not be empty"); assert.notnull(beandefinition, "beandefinition must not be null"); if (beandefinition instanceof abstractbeandefinition) { try { ((abstractbeandefinition) beandefinition).validate(); } catch (beandefinitionvalidationexception ex) { throw new beandefinitionstoreexception(beandefinition.getresourcedescription(), beanname, "validation of bean definition failed", ex); } } synchronized (this.beandefinitionmap) { object oldbeandefinition = this.beandefinitionmap.get(beanname); if (oldbeandefinition != null) { if (!this.allowbeandefinitionoverriding) { throw new beandefinitionstoreexception(beandefinition.getresourcedescription(), beanname, "cannot register bean definition [" + beandefinition + "] for bean '" + beanname + "': there is already [" + oldbeandefinition + "] bound."); } else { if (this.logger.isinfoenabled()) { this.logger.info("overriding bean definition for bean '" + beanname + "': replacing [" + oldbeandefinition + "] with [" + beandefinition + "]"); } } } else { this.beandefinitionnames.add(beanname); this.frozenbeandefinitionnames = null; } this.beandefinitionmap.put(beanname, beandefinition); resetbeandefinition(beanname); } }
代码追溯之后发现这个方法里,最关键的是以下2行:
this.beandefinitionnames.add(beanname); this.beandefinitionmap.put(beanname, beandefinition);
前者是把beanname放到队列里,后者是把beandefinition放到map中,到此注册就完成了。在后面实例化的时候,就是把beandefinitionmap中的beandefinition取出来,逐一实例化
beanfactory准备完毕之后,代码又回到了classpathxmlapplicationcontext里
public void refresh() throws beansexception, illegalstateexception { synchronized (this.startupshutdownmonitor) { // prepare this context for refreshing. preparerefresh(); // tell the subclass to refresh the internal bean factory. configurablelistablebeanfactory beanfactory = obtainfreshbeanfactory(); // prepare the bean factory for use in this context. preparebeanfactory(beanfactory); try { // allows post-processing of the bean factory in context subclasses. postprocessbeanfactory(beanfactory); // invoke factory processors registered as beans in the context. invokebeanfactorypostprocessors(beanfactory); // register bean processors that intercept bean creation. registerbeanpostprocessors(beanfactory); // initialize message source for this context. initmessagesource(); // initialize event multicaster for this context. initapplicationeventmulticaster(); // initialize other special beans in specific context subclasses. onrefresh(); // check for listener beans and register them. registerlisteners(); // instantiate all remaining (non-lazy-init) singletons. finishbeanfactoryinitialization(beanfactory); // last step: publish corresponding event. finishrefresh(); } catch (beansexception ex) { // destroy already created singletons to avoid dangling resources. destroybeans(); // reset 'active' flag. cancelrefresh(ex); // propagate exception to caller. throw ex; } } }
也就是obtainfreshbeanfactory()方法执行之后,再进行下面的步骤。
总结来说,applicationcontext将解析配置文件的工作委托给beandefinitionreader,然后beandefinitionreader将配置文件读取为xml的document文档之后,又委托给beandefinitiondocumentreader
beandefinitiondocumentreader这个组件是根据xml元素的命名空间和元素名,起到一个路由的作用,实际的解析工作,是委托给beandefinitionparserdelegate来完成的
beandefinitionparserdelegate的解析工作完成以后,会返回beandefinitionholder给beandefinitiondocumentreader,在这里,会委托给defaultlistablebeanfactory完成bean的注册
xmlbeandefinitionreader(计数、解析xml文档),beandefinitiondocumentreader(依赖xml文档,进行解析和注册),beandefinitionparserdelegate(实际的解析工作)。可以看出,在解析bean的过程中,这3个组件的分工是比较清晰的,各司其职,这种设计思想值得学习
到此为止,bean的解析、注册、spring ioc 容器的实例化过程就基本分析结束了。
推荐阅读
-
Spring源码剖析2:Spring IOC容器的加载过程
-
spring源码深度解析— IOC 之 开启 bean 的加载
-
Spring源码分析-IOC容器BeanFactory的应用场景
-
Spring Bean的实例化之属性注入源码剖析过程
-
Spring源码剖析2:Spring IOC容器的加载过程
-
Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?
-
深入了解Spring源码4:懒加载的单例Bean获取过程分析
-
Spring容器的创建过程源码解析
-
spring底层源码分析-IOC容器的初始化过程
-
# Spring IOC容器:BeanFactory、ApplicationContext和Bean的加载