Spring IOC 容器解析——容器初始化
1. 什么是IOC/DI?
IOC(InversionofControl)控制反转:所谓控制反转,就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转给容器来帮忙实现。那么必然的我们需要创建一个容器,同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们可配置的文件。
DI(DependencyInjection)依赖注入:就是指对象是被动接受依赖类而不是自己主动去找,换句话说就是指对象不是从容器中查找它依赖的类,而是在容器实例化对象的时候主动将它依赖的类注入给它。
先从我们自己设计这样一个视角来考虑:
对象和对象关系怎么表示?
可能是classpath,filesystem,或者是URL网络资源,servletContext等。
回到正题,有了配置文件,还需要对配置文件解析。
不同的配置文件对对象的描述不一样,如标准的,自定义声明式的,如何统一?在内部需要有一个统一的关于对象的定义,所有外部的描述都必须转化成统一的描述定义。
如何对不同的配置文件进行解析?需要对不同的配置文件语法,采用不同的解析器。
2. Spring容器作用及整体实现
Spring的IoC容器所起的作用,就像上图所展示的那样,它会以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。
Spring的IoC容器在实现的时候,充分运用了这两个实现阶段的不同特点,在每个阶段都加入了相应的容器扩展点,以便我们可以根据具体场景的需要加入自定义的扩展逻辑。
-
容器启动阶段
容器启动伊始,首先会通过某种途径加载Configuration MetaData。除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。下图演示了这个阶段的主要工作
总地来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。当然,一些验证性或者辅助性的工作也可以在这个阶段完成。 -
Bean实例化阶段
经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefini- tionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。
该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。
3. Spring核心容器体系结构及核心组件
1. BeanFactory
BeanFactory继承结构图.PNG先简单地了解一下上面类图中的各个类的作用。
-
AliasRegistry:定义对 alias 的简单增删改等操作。
-
SimpleAliasRegistry:主要使用 map 作为 alias 的缓存,并对接口 AliasRegistry 进行实现。
-
SingletonBeanRegistry:定义对单例的注册及获取。
-
BeanFactory :定义获取 bean 及 bean 的各种属性。
-
DefaultSingletonBeanRegistry:对接口 SingletonBeanRegistry各函数的实现,主要实现了getSingleton()以及对循环依赖的处理。
-
HierarchicalBeanFactory:继承 BeanFactory,也就是在 BeanFactory 定义的功能的基础上增加了对 parentBeanFactory 的支持。
-
BeanDefinitionRegistry:定义对 BeanDefinition的各种增删改操作。
-
FactoryBeanRegistrySupport:在 DefaultSingletonBeanRegistry的基础上增加了对FactoryBean的特殊处理功能。
-
ConfigurableBeanFactory:提供配置 Factory 的各种方法。
-
ListableBeanFactory:根据各种条件获取 bean 的配置清单。
-
AbstractBeanFactory:综合 FactoryBeanRegistrySupport 和 ConfigurableBeanFactory 的功能,主要实现了getBean()。
-
AutowireCapableBeanFactory: 提供创建 bean、 自动注入、初始化以及应用 bean 的后处理。
-
AbstractAutowireCapableBeanFactory: 综合 AbstractBeanFactory 并对接口 AutowireCapableBeanFactory 进行实现,主要实现了createBean()及依赖注入。
-
ConfigurableListableBeanFactory:BeanFactory 配置清单,指定忽略类型及接口等。
-
DefaultListableBeanFactory:综合上面所有功能,主要是对BeanDefinition的注册以及对 Bean 注册后的处理。
其中BeanFactory作为最顶层的一个接口类,它定义了IOC容器的基本功能规范,BeanFactory有三个子类:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是从上图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,他实现了所有的接口。那为何要定义这么多层次的接口呢?查阅这些接口的源码和说明发现,每个接口都有他使用的场合,它主要是为了区分在Spring内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。例如ListableBeanFactory接口表示这些Bean是可列表的,而HierarchicalBeanFactory表示的是这些Bean是有继承关系的,也就是每个Bean有可能有父Bean。AutowireCapableBeanFactory接口定义Bean的自动装配规则。这四个接口共同定义了Bean的集合、Bean之间的关系、以及Bean行为。
DefaultListableBeanFactory 是整个bean 加载的核心部分 ,是 Spring 注册及加载 Bean的默认实现。DefaultListableBeanFactory 继承了 AbstractAutowireCapableBeanFactory 并实现了 ContigurableListableBeanFactory 以及 BeanDefinitionRegistry 接口。
最基本的IOC容器接口BeanFactory
BeanFactory的源码.png在BeanFactory里只对IOC容器的基本行为作了定义,根本不关心你的Bean是如何定义怎样加载的。正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心。
而要知道工厂是如何产生对象的,我们需要看具体的IOC容器实现,Spring提供了许多IOC容器的实现。比如ClasspathXmlApplicationContext等。
ApplicationContext是Spring提供的一个高级的IOC容器,它除了能够提供IOC容器的基本功能外,还为用户提供了以下的附加服务。
从ApplicationContext接口的实现,我们看出其特点:
- 支持信息源,可以实现国际化。(实现MessageSource接口)
- 访问资源。(实现ResourcePatternResolver接口,后面章节会讲到)
- 支持应用事件。(实现ApplicationEventPublisher接口)
2. BeanDefinition
BeanDefinition 是一个接口 ,在 Spring 中存在三种实现 :RootBeanDefinition 、ChildBeanDefinition 以及 GenericBeanDefinition 。三种实现均继承了 AbstractBeanDefiniton ,其中 BeanDefinition 是配置文件<bean>元素标签在容器中的内部表示形式。<bean>元素标签拥有 class、scope、lazy-init 等配置属性,BeanDefinition 则提供了相应的 beanClass、scope、lazylnit属性,BeanDefinition 和<bean>中的属性是一一对应的。其中RootBeanDefinition 是最常用的实现类 ,它对应一般性的<bean>元素标签 ,GenericBeanDefinition 是自2.5 版本以后新加入的 bean 文件配置属性定义类,是一站式服务类。
在配置文件中可以定义父<bean>和子<bean>,父<bean>用 RooteanDefinition 表示 ,而子<bean>用 ChildBeanDefiniton 表示 ,而没有父<bean>的<bean>就使用 RootBeanDefinition 表示 。AbstractBeanDefinition 对两者共同的类信息进行抽象。
3. 配置文件封装
Spring 的配置文件读取是通过 ClassPathResource 进行封装的,如 new ClassPathResource("beanFactoryTest.xml") ,那么 ClassPathResource 完成了什么功能呢?
在 Java 中,将不同来源的资源、抽象成 URL ,通过注册不同的 handler ( URLStreamHandler ) 来处理不同来源的资源的读取逻辑 ,一般 handler 的类型使用不同前缀 ( 协议,Protocol ) 来识别,如 “file:”、“http:” 、“'jar:” 等,然而 URL 没有默认定义相对 ClassPath或 ServletContext 等资源的 handler ,虽然可以注册自己的 URLStreamHandler 来解析特定的 URL 前缀 ( 协议 ), 比如 “classpath:”,然而这需要了解 URL 的实现机制,而且 URL 也没有提供一些基本的方法 , 如检查当前资源是否存在、检查当前资源是否可读等方法。因而 Spring 对其内部使用到的资源实现了自己的抽象结构 :Resource 接口来封装底层资源。
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
String getFilename();
String getDescription();
}
lnputStreamSource 封装任何能返回 lnputStream 的类 ,比如 File 、Classpath 下的资源和 Byte Array 等。它只有一个方法定义 :getlnputStream(),该方法返回一个新的 lnputStream 对象。
Resource 接口抽象了所有 Spring 内部使用到的底层资源 :File 、URL 、Classpath 等。首先, 它定义了 3 个判断当前资源状态的方法 :存在性 ( exists )、可读性 ( isReadable )、是否处于打开状态 ( isOpen )。另外,Resource 接口还提供了不同资源到 URL 、URI 、File 类型的转换,以及获取 lastModified 属性、文件名 ( 不带路径信息的文件名,getFilename() )的方法。为了便于操作,Resource 还提供了基于当前资源、创建一个相对资源的方法 :createRelative()。在错误处理中需耍详细地打印出错的资源文件 ,因而 Resource 还提供了getDescription()方法用于在错误处理中的打印信息。
在日常的开发工作中,资源文件的加载也是经常用到的,可以直接使用 Spring 提供的类, 比如在希望加载文件时可以使用以下代码:
Resource resource = new ClassPathResurce("beanFactoryTest.xml");
InputStream inputStream = resource.getInputStream () ;
得到 inputStream 后,我们就可以按照以前的开发方式进行实现了,并且我们已经可以利用Resource 及其子类为我们提供好的诸多特性。
有了Resource 接口便可以对所有资源、文件进行统一处理。至于实现 ,其实是非常简单的, 以getlnputStream 为例,ClassPathResource 中的实现方式便是通过 class 或者 classLoader 提供的底层方法进行调用,而对于 FileSystemResource 的实现其实更简单,直接使用 FilelnputStream 对文件进行实例化。
当通过 Resource 相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader 来处理了。
4. XML 配置文件的读取
XML 配置文件的读取是 Spring 中重要的功能,因为 Spring 的大部分功能都是以配置作为切入点的,那么我们可以从 XmlBeanDefinitionReader 中梳理一下资源文件读取、解析及注册的大致脉络,首先我们看着各个类的功能。
- ResourceLoader :定义资源、加载器 ,主要应用于根据给定的资源文件地址返回对应的Resource。
- BeanDefinitionReader:主要定义资源文件读取并转换为 BeanDefinition 的各个功能。
- EnvironmentCapable:定义获取 Environment的方法。
- DocumentLoader:定义从资源文件加载到转换为 Documnent 的功能
- AbstractBeanDefinitionReader:对 EnvironmentCapable、BeanDefinitionReader 类定义的功能进行实现。
- BeanDefinitionDocurnentReader :定义读取 Docuernnt 并注册 BeanDefinition 功能。
- BeanDefinitionParserDelegate:定义解析 Element 的各种方法。
经过以上分析,我们可以梳理出整个XML配置文件读取的大致流程 ,如图所示,在
(1)通过继承自AbstractBeanDefinitionReader 中的方法,来使用 ResourLoader 将资源文件路径转换为对应的Resource文件。
(2)通过 DocumentLoader 对 Resource 文件进行转换,将 Resource 文件转换为 Document文件。
(3)通过实现接口BeanDefinitionDocumentReader 的DefaultBeanDefinitionDocumentReader 类对 Document 进行解析,并使用 BeanDefinitionParserDelegate 对 Element 进行解析。
下面重点分析一下ResourceLoader相关的内容
org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定 位策略则由相应的ResourceLoader实现类给出。ResourceLoader定义如下:
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
ClassLoader getClassLoader();
}
其中最主要的就是Resource getResource(String location);方法,通过它,我们就可以根据指定的资源位置,定位到具体的资源实例。
DefaultResourceLoader:
ResourceLoader有一个默认的实现类,即org.springframework.core.io.DefaultResource-
Loader,该类默认的资源查找处理逻辑如下。
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
//如果是类路径的方式,那需要使用ClassPathResource来得到Bean文件的资源对象。
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
//如果是URL方式,使用URLResource来得到Bean文件的资源对象。
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
//都不是,调用容器本身的getResourceByPath(location)获取Resource.
return getResourceByPath(location);
}
}
}
(1) 首先检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类 型资源并返回。
(2) 否则,(a) 尝试通过URL,根据资源路径来定位资源,如果没有抛出MalformedURLException, 有则会构造UrlResource类型的资源并返回;
(b) 如果还是无法根据资源路径定位指定的资源,则委派 getResourceByPath(String) 方法来定位, DefaultResourceLoader的getResourceByPath(String)方法默认实现逻辑是,构造ClassPathResource类型的资源并返回。
FileSystemResourceLoader:
为了避免DefaultResourceLoader在最后getResourceByPath(String)方法上的不恰当处理, 我们可以使用org.springframework.core.io.FileSystemResourceLoader,它继承自Default- ResourceLoader,但覆写了 getResourceByPath(String)方法,使之从文件系统加载资源并以FileSystemResource类型返回。
FileSystemResourceLoader 在 ResourceLoader 家族中的兄弟 FileSystemXmlApplicationContext,也是覆写了getResourceByPath(String)方法的逻辑,以改变DefaultResourceLoader的默认资源加载行为,最终从文件系统中加载并返回FileSystemResource类型的资源。
ResourcePatternResolver——批量查找的ResourceLoader:
ResourcePatternResolver是ResourceLoader的扩展,ResourceLoader每次只能根据资源路径 返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式, 每次返回多个Resource实例。接口org.springframework.core.io.support.ResourcePattern-
Resolver定义如下:
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
ResourcePatternResolver在继承ResourceLoader原有定义的基础上,又引入了Resource[] getResources(String)方法定义,以支持根据路径匹配模式返回多个Resources的功能。它同时还引入了一种新的协议前缀classpath*:,针对这一点的支持,将由相应的子类实现。
ResourcePatternResolver最常用的一个实现是 org.springframework.core.io.support.PathMatchingResourcePatternResolver,该实现类支持ResourceLoader级别的资源加载,支持基于Ant风格的路径匹配模式,支持ResourcePatternResolver新增加的classpath*:前缀等,基本上集所有技能于一身。
在构造PathMatchingResourcePatternResolver实例的时候,可以指定一个ResourceLoader, 如果不指定的话, 则PathMatchingResourcePatternResolver内部会默认构造一个DefaultResourceLoader实例。PathMatchingResourcePatternResolver内部会将匹配后确定的资源路径, 委派给它的ResourceLoader来查找和定位资源。这样,如果不指定任何ResourceLoader的话,PathMatchingResourcePatternResolver在加载资源的行为上会与DefaultResourceLoader基本相同, 只存在返回的Resource数量上的差异。
ApplicationContext与ResourceLoader:
ApplicationContext继承了ResourcePatternResolver,当然就间接实现了ResourceLoader接口。所以,任何的ApplicationContext实现都可以看作是一个 ResourceLoader甚至ResourcePatternResolver。而这就是ApplicationContext支持Spring内统一 资源加载策略的真相。
通常,所有的ApplicationContext 实现类会直接或者间接地继承 org.springframework.context.support.AbstractApplicationContext,从这个类上,我们就可以看到ApplicationContext与ResourceLoader之间的所有关系。AbstractApplicationContext继承了DefaultResourceLoader,那么,它的getResource(String)当然就直接用DefaultResourceLoader的了。剩下需要它“效劳”的,就是ResourcePatternResolver的Resource[] getResources (String),当然,AbstractApplicationContext也不负众望,当即拿下。AbstractApplicationContext类的内部声明有一个resourcePatternResolver,类型是ResourcePatternResolver,对应的实例类型为PathMatchingResourcePatternResolver 。之前我们说过 PathMatchingResourcePatternResolver构造的时候会接受一个ResourceLoader,而AbstractApplicationContext本身又继承自 DefaultResourceLoader,当然就直接把自身给“贡献”了。这样,整个ApplicationContext的实现类就完全可以支持ResourceLoader或者ResourcePatternResolver接口。说白了,ApplicationContext的实现类在作为ResourceLoader或者ResourcePatternResolver时候的行为,完全就是委派给了PathMatchingResourcePatternResolver和DefaultResourceLoader来做。下图给出了AbstractApplicationContext与 ResourceLoader和ResourcePatternResolver之间的类层次关系。
4. 容器初始化
IOC容器的初始化包括BeanDefinition的Resource定位、载入和注册这三个基本的过程。我们以ApplicationContext为例讲解,ApplicationContext系列容器也许是我们最熟悉的,因为Web项目中使用的XmlWebApplicationContext就属于这个继承体系,还有ClasspathXmlApplicationContext等,其继承体系如下图所示: ApplicationContext的继承体系.PNGApplicationContext允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。对于Bean的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的Spring应用提供了一个共享的Bean定义环境。
下面我们分别简单地演示一下两种IOC容器的创建过程:
- 通过DefaultListableBeanFactory创建 通过DefaultListableBeanFactory创建.png
- 通过ClassPathXmlApplicationContext创建:
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
通过ApplicationContext初始化容器源码剖析
1. 调用构造方法
其实际调用的构造函数为:2. 设置资源加载器和资源定位
通过分析ClassPathXmlApplicationContext的源代码可以知道,在创建ClassPathXmlApplicationContext容器时,构造方法做以下两项重要工作:
首先,调用父类容器的构造方法(super(parent)方法)为容器设置好Bean资源加载器。
然后,再调用父类AbstractRefreshableConfigApplicationContext的setConfigLocations(configLocations)方法设置Bean定义资源文件的定位路径。
ClasspathResourceres=newClasspathResource(“a.xml,b.xml,......”);多个资源文件路径之间可以是用”,;\t\n”等分隔。
B.ClasspathResourceres=newClasspathResource(newString[]{“a.xml”,”b.xml”,......});至此,SpringIOC容器在初始化时将配置的Bean定义资源文件定位为Spring封装的Resource。
3. AbstractApplicationContext的refresh函数载入Bean定义过程:
SpringIOC容器对Bean定义资源的载入是从refresh()函数开始的,refresh()是一个模板方法,refresh()方法的作用是:在创建IOC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IOC容器。refresh的作用类似于对IOC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。
refresh()方法的作用是:在创建IOC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IOC容器。refresh的作用类似于对IOC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。
4. AbstractApplicationContext的obtainFreshBeanFactory()方法调用子类容器的refreshBeanFactory()方法,启动容器载入Bean定义资源文件的过程
代码如下: AbstractApplicationContext类中只抽象定义了refreshBeanFactory()方法,容器真正调用的是其子类AbstractRefreshableApplicationContext实现的refreshBeanFactory()方法,方法的源码如下: 在这个方法中,先判断BeanFactory是否存在,如果存在则先销毁beans并关闭beanFactory,接着创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载bean定义。5. AbstractRefreshableApplicationContext子类的loadBeanDefinitions方法:
AbstractRefreshableApplicationContext中只定义了抽象的loadBeanDefinitions方法,容器真正调用的是其子类AbstractXmlApplicationContext对该方法的实现,AbstractXmlApplicationContext的主要源码如下:loadBeanDefinitions方法同样是抽象方法,是由其子类实现的,也即在AbstractXmlApplicationContext中。XmlBean读取器(XmlBeanDefinitionReader)调用其父类AbstractBeanDefinitionReader的reader.loadBeanDefinitions方法读取Bean定义资源。由于我们使用FileSystemXmlApplicationContext作为例子分析,因此getConfigResources的返回值为null,因此程序执行reader.loadBeanDefinitions(configLocations)分支。
6. AbstractBeanDefinitionReader读取Bean定义资源,在其抽象父类AbstractBeanDefinitionReader中定义了载入过程。
AbstractBeanDefinitionReader的loadBeanDefinitions方法源码如下:loadBeanDefinitions(Resource...resources)方法和上面分析的3个方法类似,同样也是调用XmlBeanDefinitionReader的loadBeanDefinitions方法。
从对AbstractBeanDefinitionReader的loadBeanDefinitions方法源码分析可以看出该方法做了以下两件事:
7. 资源加载器获取要读入的资源:
XmlBeanDefinitionReader通过调用其父类DefaultResourceLoader的getResource方法获取要加载的资源,其源码如下: getResourceByPath方法就是为了处理既不是classpath标识,又不是URL标识的Resource定位这种情况。 这样就创建了ClassPathResource来完成从文件系统得到配置文件的资源定义。这样,就可以从文件系统路径上对IOC配置文件进行加载,当然我们可以按照这个逻辑从任何地方加载,在Spring中我们看到它提供的各种资源抽象,比如URLResource,FileSystemResource等来供我们使用。上面我们看到的是定位Resource的一个过程,而这只是加载过程的一部分。
8. XmlBeanDefinitionReader加载Bean定义资源:
继续回到XmlBeanDefinitionReader的loadBeanDefinitions(Resource...)方法看到代表bean文件的资源定义以后的载入过程。通过源码分析,载入Bean定义资源文件的最后一步是将Bean定义资源转换为Document对象,该过程由documentLoader实现。
9. DocumentLoader将Bean定义资源转换为Document对象:
DocumentLoader将Bean定义资源转换成Document对象的源码如下:该解析过程调用JavaEE标准的JAXP标准进行处理。
至此SpringIOC容器根据定位的Bean定义资源文件,将其加载读入并转换成为Document对象过程完成。接下来我们要继续分析SpringIOC容器将载入的Bean定义资源文件转换为Document对象之后,是如何将其解析为SpringIOC管理的Bean对象并将其注册到容器中的。
10. XmlBeanDefinitionReader解析载入的Bean定义资源文件:
XmlBeanDefinitionReader类中的doLoadBeanDefinitions方法是从特定XML文件中实际载入Bean定义资源的方法,该方法在载入Bean定义资源之后将其转换为Document对象,接下来调用registerBeanDefinitions启动SpringIOC容器对Bean定义的解析过程,registerBeanDefinitions方法源码如下:Bean定义资源的载入解析分为以下两个过程:
首先,通过调用XML解析器将Bean定义资源文件转换得到Document对象,但是这些Document对象并没有按照Spring的Bean规则进行解析。这一步是载入的过程。
其次,在完成通用的XML解析之后,按照Spring的Bean规则对Document对象进行解析。
按照Spring的Bean规则对Document对象解析的过程是在接口BeanDefinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader中实现的。
11. DefaultBeanDefinitionDocumentReader对Bean定义的Document对象解析:
BeanDefinitionDocumentReader接口通过registerBeanDefinitions方法调用其实现类DefaultBeanDefinitionDocumentReader对Document对象进行解析,解析的代码如下:通过上述SpringIOC容器对载入的Bean定义Document解析可以看出,我们使用Spring时,在Spring配置文件中可以使用<import>元素来导入IOC容器所需要的其他资源,SpringIOC容器在解析时会首先将指定导入的资源加载进容器中。使用<ailas>别名时,SpringIOC容器首先将别名元素所定义的别名注册到容器中。
对于既不是<import>元素,又不是<alias>元素的元素,即Spring配置文件中普通的<bean>元素的解析由BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法来实现。
12. BeanDefinitionParserDelegate解析Bean定义资源文件中的<bean>元素:
Bean定义资源文件中的<import>和<alias>元素解析在DefaultBeanDefinitionDocumentReader中已经完成,对Bean定义资源文件中使用最多的<bean>元素交由BeanDefinitionParserDelegate来解析,其解析实现的源码如下:只要使用过Spring,对Spring配置文件比较熟悉的人,通过对上述源码的分析,就会明白我们在Spring配置文件中<Bean>元素的中配置的属性就是通过该方法解析和设置到Bean中去的。
注意:在解析<Bean>元素过程中没有创建和实例化Bean对象,只是创建了Bean对象的定义类BeanDefinition,将<Bean>元素中的配置信息设置到BeanDefinition中作为记录,当依赖注入时才使用这些记录信息创建和实例化具体的Bean对象。
上面方法中一些对一些配置如元信息(meta)、qualifier等的解析,我们在Spring中配置时使用的也不多,我们在使用Spring的<Bean>元素时,配置最多的是<property>属性,因此我们下面继续分析源码,了解Bean的属性在解析时是如何设置的。
13. BeanDefinitionParserDelegate解析<property>元素:
BeanDefinitionParserDelegate在解析<Bean>调用parsePropertyElements方法解析<Bean>元素中的<property>属性子元素,解析源码如下:通过对上述源码的分析,我们可以了解在Spring配置文件中,<Bean>元素中<property>元素的相关配置是如何处理的:
a.ref被封装为指向依赖对象一个引用。
b.value配置都会封装成一个字符串类型的对象。
c.ref和value都通过“解析的数据类型属性值.setSource(extractSource(ele));”方法将属性值/引用与所引用的属性关联起来。
在方法的最后对于<property>元素的子元素通过parsePropertySubElement方法解析,我们继续分析该方法的源码,了解其解析过程。
14. 解析<property>元素的子元素:
在BeanDefinitionParserDelegate类中的parsePropertySubElement方法对<property>中的子元素解析,源码如下:通过上述源码分析,我们明白了在Spring配置文件中,对<property>元素中配置的array、list、set、map、prop等各种集合子元素的都通过上述方法解析,生成对应的数据对象,比如ManagedList、ManagedArray、ManagedSet等,这些Managed类是Spring对象BeanDefiniton的数据封装,对集合数据类型的具体解析有各自的解析方法实现,解析方法的命名非常规范,一目了然,我们对<list>集合元素的解析方法进行源码分析,了解其实现过程。
15. 解析<list>子元素:
在BeanDefinitionParserDelegate类中的parseListElement方法就是具体实现解析<property>元素中的<list>集合子元素,源码如下:经过对SpringBean定义资源文件转换的Document对象中的元素层层解析,SpringIOC现在已经将XML形式定义的Bean定义资源文件转换为SpringIOC所识别的数据结构——BeanDefinition,它是Bean定义资源文件中配置的POJO对象在SpringIOC容器中的映射,我们可以通过AbstractBeanDefinition为入口,看到了IOC容器进行索引、查询和操作。
通过SpringIOC容器对Bean定义资源的解析后,IOC容器大致完成了管理Bean对象的准备工作,即初始化过程,但是最为重要的依赖注入还没有发生,现在在IOC容器中BeanDefinition存储的只是一些静态信息,接下来需要向容器注册Bean定义信息才能全部完成IOC容器的初始化过程。
16. 解析过后的BeanDefinition在IOC容器中的注册:
让我们继续跟踪程序的执行顺序,接下来我们来分析DefaultBeanDefinitionDocumentReader对Bean定义转换的Document对象解析的流程中,在其parseDefaultElement方法中完成对Document对象的解析后得到封装BeanDefinition的BeanDefinitionHold对象,然后调用BeanDefinitionReaderUtils的registerBeanDefinition方法向IOC容器注册解析的Bean,BeanDefinitionReaderUtils的注册的源码如下:当调用BeanDefinitionReaderUtils向IOC容器注册解析的BeanDefinition时,真正完成注册功能的是DefaultListableBeanFactory。
17. DefaultListableBeanFactory向IOC容器注册解析后的BeanDefinition:
DefaultListableBeanFactory中使用一个HashMap的集合对象存放IOC容器中注册解析的BeanDefinition,向IOC容器注册的主要源码如下:至此,Bean定义资源文件中配置的Bean被解析过后,已经注册到IOC容器中,被容器管理起来,真正完成了IOC容器初始化所做的全部工作。现在IOC容器中已经建立了整个Bean的配置信息,这些BeanDefinition信息已经可以使用,并且可以被检索,IOC容器的作用就是对这些注册的Bean定义信息进行处理和维护。这些的注册的Bean定义信息是IOC容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入。
总结:
现在通过上面的代码,总结一下IOC容器初始化的基本步骤:
(1).初始化的入口在容器实现中的refresh()调用来完成。
(2).对bean定义载入IOC容器使用的方法是loadBeanDefinition,其中的大致过程如下:
通过ResourceLoader来完成资源文件位置的定位,DefaultResourceLoader是默认的实现,同时上下文本身就给出了ResourceLoader的实现,可以从类路径,文件系统,URL等方式来定为资源位置。如果是XmlBeanFactory作为IOC容器,那么需要为它指定bean定义的资源,也就是说bean定义文件时通过抽象成Resource来被IOC容器处理的,容器通过BeanDefinitionReader来完成定义信息的解析和Bean信息的注册,往往使用的是XmlBeanDefinitionReader来解析bean的xml定义文件-实际的处理过程是委托给BeanDefinitionParserDelegate来完成的,从而得到bean的定义信息,这些信息在Spring中使用BeanDefinition对象来表示-这个名字可以让我们想到loadBeanDefinition,RegisterBeanDefinition这些相关方法-他们都是为处理BeanDefinitin服务的,容器解析得到BeanDefinition以后,需要把它在IOC容器中注册,这由IOC实现BeanDefinitionRegistry接口来实现。注册过程就是在IOC容器内部维护的一个HashMap来保存得到的BeanDefinition的过程。这个HashMap是IOC容器持有Bean信息的场所,以后对Bean的操作都是围绕这个HashMap来实现的。
然后我们就可以通过BeanFactory和ApplicationContext来享受到SpringIOC的服务了,在使用IOC容器的时候,我们注意到除了少量粘合代码,绝大多数以正确IOC风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。Spring本身提供了对声明式载入web应用程序用法的应用程序上下文,并将其存储在ServletContext中的框架实现。
BeanFactory和FactoryBean,其中BeanFactory指的是IOC容器的编程抽象,比如ApplicationContext,XmlBeanFactory等,这些都是IOC容器的具体表现,需要使用什么样的容器由客户决定,但Spring为我们提供了丰富的选择。FactoryBean只是一个可以在IOC而容器中被管理的一个Bean,是对各种处理过程和资源使用的抽象,FactoryBean在需要时产生另一个对象,而不返回FactoryBean本身,我们可以把它看成是一个抽象工厂,对它的调用返回的是工厂生产的产品。所有的FactoryBean都实现特殊的org.springframework.beans.factory.FactoryBean接口,当使用容器中FactoryBean的时候,该容器不会返回FactoryBean本身,而是返回其生成的对象。Spring包括了大部分的通用资源和服务访问抽象的FactoryBean的实现,其中包括:对JNDI查询的处理,对代理对象的处理,对事务性代理的处理,对RMI代理的处理等,这些我们都可以看成是具体的工厂,看成是Spring为我们建立好的工厂。也就是说Spring通过使用抽象工厂模式为我们准备了一系列工厂来生产一些特定的对象,免除我们手工重复的工作,我们要使用时只需要在IOC容器里配置好就能很方便的使用了。