天天看点

看看Spring的源码(一)——Bean加载过程

最近几天跟同事聊起spring的一些问题,对一些地方有些疑问,趁这两天有点空,看看spring的源码,了解下具体的实现细节。本文基于spring 4.0.5版本。

首先web项目使用spring是通过在web.xml里面配置

<code>org.springframework.web.context.contextloaderlistener</code>初始化ioc容器的。

那就以此为切入点顺藤摸瓜。

<code>contextloaderlistener</code>继承了<code>contextloader</code>,并且实现<code>servletcontextlistener</code>接口。当server容器(一般指tomcat)启动时,会收到事件初始化。<code></code>

<code></code>

<code>initwebapplicationcontext</code>方法是在<code>org.springframework.web.context.contextloader</code>类里面。方法太长,分段读一下。<code></code>

首先是判断<code>servletcontext</code>中是否已经注册了<code>webapplicationcontext</code>,如果有则抛出异常,避免重复注册。然后就是启用log,启动计时。本方法的关键就在于try代码块里的内容<code></code>

这里面有几个关键的方法。首先看一下<code>createwebapplicationcontext()</code><code></code>

首先<code>determinecontextclass()</code>方法查明具体的<code>context</code>类,他会读取<code>servletcontext</code>的初始化参数<code>contextclass</code>,此参数我们一半不配置,所以<code>spring</code>就会读取跟<code>org.springframework.web.context.webapplicationcontext</code>同一个包下面的<code>contextloader.properties</code>文件读取默认设置,反射出<code>org.springframework.web.context.support.xmlwebapplicationcontext</code>类来。接下来就是在<code>configureandrefreshwebapplicationcontext()</code>方法里将新创建的<code>xmlwebapplicationcontext</code>进行初始化。首先会设置一个默认id,即<code>org.springframework.web.context.webapplicationcontext:</code>+你项目的<code>contextpath</code>。<code></code>

紧接着就是将<code>servletcontext</code>设置成<code>xmlwebapplicationcontext</code>的属性,这样<code>spring</code>就能在上下文里轻松拿到<code>servletcontext</code>了。<code></code>

接下来就是读取<code>web.xml</code>文件中的<code>contextconfiglocation</code>参数。如果没有配置就会去读<code>web-inf下的applicationcontext.xml</code>文件。<code></code>

并将值设置(就是我们的spring配置文件的路径)进<code>xmlwebapplicationcontext</code>中。然后就会在指定的路径加载配置文件。<code></code>

接下来就是<code>customizecontext(sc, wac)</code>方法,此方法会根据用户配置的<code>globalinitializerclasses</code>参数来初始化一些用户自定义的属性,一般我们不配置,所以这里什么也不做。

最后登场的就是最核心的方法了,<code></code>

在这个方法里,会完成资源文件的加载、配置文件解析、bean定义的注册、组件的初始化等核心工作,我们一探究竟。<code></code>

次方法是同步的,避免重复刷新,每个步骤都放在单独的方法内,流程清晰,是值得学习的地方。这里面有个重要的方法是<code>finishbeanfactoryinitialization(beanfactory);</code>,里面的内容是spring如何实例化bean,并注入依赖的,这个内容下一节讲,本节只说明spring是如何加载class文件的。

首先就是<code>preparerefresh()</code>方法。<code></code>

此方法做一些准备工作,如记录开始时间,输出日志,<code>initpropertysources();</code>和<code>getenvironment().validaterequiredproperties();</code>一般没干什么事。

接下来就是初始化<code>beanfactory</code>,是整个<code>refresh()</code>方法的核心,其中完成了配置文件的加载、解析、注册

看看它里面都做了些什么?<code></code>

首先<code>refreshbeanfactory()</code>:<code></code>

我们看到会创建一个<code>defaultlistablebeanfactory</code>实例<code></code>

再设置一个id<code></code>

然后设置一些自定义参数:<code></code>

这里面最重要的就是<code>loadbeandefinitions(beanfactory);</code>方法了。<code></code>

此方法会通过<code>xmlbeandefinitionreader</code>加载bean定义。具体的实现方法是在<code>org.springframework.beans.factory.xml.xmlbeandefinitionreader.loadbeandefinitions</code>方法中定义的。这里设计了层层调用,有好多重载方法,主要就是加载spring所有的配置文件(可能会有多个),以备后面解析,注册之用。我一路追踪到<code>org.springframework.beans.factory.xml.defaultbeandefinitiondocumentreader.doregisterbeandefinitions(element root)</code><code></code>

这里创建了一个<code>beandefinitionparserdelegate</code>示例,解析xml的过程就是委托它完成的,我们不关心它是怎样解析xml的,我们只关心是怎么加载类的,所以就要看<code>parsebeandefinitions(root, this.delegate)</code>方法了。<code></code>

我们看到最终解析xml元素的是<code>delegate.parsecustomelement(ele)</code>方法,最终会走到一下方法.<code></code>

这里会根据不同的xml节点,会委托<code>namespacehandlersupport</code>找出合适的<code>beandefinitionparser</code>,如果我们配置了<code></code>

那么对应<code>beandefinitionparser</code>就是<code>org.springframework.context.annotation.componentscanbeandefinitionparser</code>,来看看它的<code>parse</code>方法。<code></code>

不难看出这里定义了一个<code>classpathbeandefinitionscanner</code>,通过它去扫描包中的类文件,注意:这里是类文件而不是类,因为现在这些类还没有被加载,只是classloader能找到这些class的路径而已。到目前为止,感觉真想距离我们越来越近了。顺着继续往下摸。进入<code>dosacn</code>方法里,映入眼帘的又是一大坨代码,但是我们只关心观点的部分。<code></code>

一眼就能看出是通过<code></code>

有时候不得不佩服这些外国人起名字的功力,把扫描出来的类叫做candidates(候选人);真是不服不行啊,这种名字真的很容易理解有不有?哈哈,貌似扯远了。继续往下看。这里只列出方法的主题部分。<code></code>

先看这两句:<code></code>

假设我们配置的需要扫描的包名为<code>com.geeekr.service</code>,那么<code>packagesearchpath</code>的值就是<code>classpath*:com.geeekr.service/**/*.class</code>,意思就是com.geeekr.service包(包括子包)下所有class文件;如果配置的是<code>*</code>,那么<code>packagesearchpath</code>的值就是<code>classpath*:*/**/*.class</code>。这里的表达式是spring自己定义的。spring会根据这种表达式找出相关的class文件。<code></code>

这一句就把相关class文件加载出来了,那我们就要看看,spring究竟是如何把class文件找到的了。首先看看<code>resourcepatternresolver</code>的定义:<code></code>

进入<code>getresources</code>方法<code></code>

这里会先判断表达式是否以<code>classpath*:</code>开头。前面我们看到spring已经给我们添加了这个头,这里当然符合条件了。接着会进入<code>findpathmatchingresources</code>方法。在这里又把<code>**/*.class</code>去掉了,然后在调用<code>getresources</code>方法,然后在进入<code>findallclasspathresources</code>方法。这里的参数只剩下包名了例如<code>com/geeekr/service/</code>。<code></code>

真相大白了,spring也是用的<code>classloader</code>加载的class文件。一路追踪,原始的classloader是<code>thread.currentthread().getcontextclassloader();</code>。到此为止,就拿到class文件了。

spring会将class信息封装成<code>beandefinition</code>,然后再放进<code>defaultlistablebeanfactory</code>的<code>beandefinitionmap</code>中。

拿到了class文件后,就要看看spring是如何装配bean的了,下一节,继续看。