一、概述

  IOC容器就是具有依赖注入功能的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象之间的依赖。应用程序无需直接在代码中new 相关的对象,应用程序由IOC容器进行组装。在Spring中BeanFactory是IOC容器的实际代表者。

  由IOC容器管理的那些组成你应用程序的对象我们就叫它Bean,Bean就是由Spring容器初始化、装配及管理的对象。

  Spring提供了两种容器:BeanFactory和ApplicationContext。

  BeanFactory:基础类型IOC容器,提供完整的IoC服务,默认采用延迟初始化策略,也就是只有客户端对象需要访问容器中的某个受管理对象的时候,才对该受管理对象进行初始化以及依赖注入操作。

  ApplicationContext:是在BeanFactory的基础上构建,除了拥有BeanFactory的所有支持,还提供了其他高级特性,例如事件发布、国际化支持等。ApplicationContext所管理的对象,在容器启动后默认全部初始化并绑定完成,所以相对于BeanFactory,它要求更多的资源。

二、BeanFactory的对象注册与依赖绑定

  XML配置格式是Spring支持的最完整、功能最强大的配置方式。

  1、配置文件封装

  Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源。

  public interface InputStreamSource
{
InputStream getInputStream() throws IOException;
}

  InputStreamResource封装任何返回InputStream的类,比如File、ClassPath下的资源和Byte Array等。

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 relativePath) throws IOException;
String getFilename();
String getDescription();
}

  Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、classPath等。对不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、ClassPath资源(ClassPathResource)、URL资源(URLResource)等。相关类图如下:

  

  在日常的开发中,我们可以直接使用Spring提供的类来进行资源的加载,比如:

public class Test
{
public static void main(String[] args) throws IOException
{
BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("xmlBeanFactory.xml"));
MyTestBean myTestBean=(MyTestBean) beanFactory.getBean("myTestBean");
myTestBean.fun();
//加载ClassPath资源
Resource resource=new ClassPathResource("test.txt");
InputStream inputStream=resource.getInputStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
System.out.println(reader.readLine());
//加载File文件资源
Resource resource=new FileSystemResource("D:\\fileResourceTest.txt");
InputStream inputStream=resource.getInputStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
String str;
while((str=reader.readLine())!=null)
System.out.println(str);
}
}

  2、加载Bean

  当通过Resource相关类完成了对配置文件封装后,配置文件的读取工作就全权交给XmlBeanDifinitionReader来处理了。XmlBeanDifinitionReader负责读取Spring指定格式的XML配置文件并解析,之后将解析后的文件内容映射到相应的BeanDefinition,并加载到相应的BeanDefinitionRegistry中。这时,整个BeanFactory就可以放给客户端使用了。

  

  整个资源的加载过程很复杂,参考下面的时序图

  

  整个流程大致要经过以下几个步骤:

  1. 封装资源文件。进入XmlBeanDefinitionReader后,首先使用EncodedResource类对resource进行封装。
  2. 获取输入流,从Resource中获取对应的InputStream并构造。
  3. 通过构造的InputStream实例和resource实例继续调用doLoadBeanDefinitions。

  首先对传入的Resource资源做封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
/*
* 加载XML文件,并得到相应的、Document
* 根据返回的Document注册Bean信息
*/
Document doc = doLoadDocument(inputSource, 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的代码中,执行下列代码

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception
{
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}

  DocumentLoader只是一个接口,这里真正调用的是DefaultDocumentLoader。

@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}

  与SAX解析XML文档的思路一致,这里首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析InputSource来返回Document对象。DocumentLoader还涉及到验证模式的读取。

  备注:

  验证模式,XML文件的验证模式保证了XML文件的正确性,比较常用的有两种,DTD和XSD。

  DTD:文档定义类型,属于XML文件组成的一部分,是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签是否正确。一个DTD文件包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。

  XSD:即XML schema。描述了XML文档的结构,可以使用一个指定的XML schema来验证某个XML文档,以坚持该XML文档是否符合其要求。

  Spring通过getValidationModeForResource方法来获取对对应资源的验证模式:

protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果手动指定了验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果未指定,则使用自动检测
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}

  当把文件转换为Document后,接下来就是提取并注册bean了,也就是执行registerBeanDefinitions(doc, resource)方法。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException
{
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//将环境变量设置其中
documentReader.setEnvironment(getEnvironment());
//记录统计前BeanDefinition的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
//加载和注册bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//记录本次加载的BeanDefinition的个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}

  进入registerBeanDefinitions方法:

@Override
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)
{
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
// 处理profile属性
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
//解析前处理,留给子类实现
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
//解析后处理,留给子类处理
postProcessXml(root);
this.delegate = parent;
}

  处理了profile后就进行XML读取了,跟踪代码进入parseBeanDefinitions方法。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对beans的处理
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)) {
//对bean的处理
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}

三、ApplicationContext

  Spring为基本的BeanFactory类型容器提供了XMLBeanFactory实现,相应地,它也为ApplicationContext类型容器提供了以下几个实现:

  • FileSystemApplicationContext:从文件系统加载bean定义以及相关资源的实现
  • ClassPathXmlApplicationContext:从CLASSPATH加载bean定义以及相关资源的实现
  • XMLWebApplicationContext:用于Web应用程序的实现

  1、统一资源加载策略

  Spring框架内部使用Resource接口作为所有资源的抽象和访问接口,上面已有接触,其中ClassPathResource就是Resource的一个特定类型的实现,代表位于Classpath中的资源。有了资源,还需要ResourceLoader去查找和定位这些资源。

  ResourceLoader有一个默认的实现类DefaultResourceLoader,实现代码如下:

@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
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 url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}

  处理逻辑如下:

  1. 首选检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类型资源并返回
  2. 否则,尝试通过url,根据资源路径来定位资源
  3. 如果还没有,则委派getResourceByPath方法来定位

  ResourcePatternResolver是ResourceLoader的扩展,ResourceLoader每次只能根据资源路径返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式,每次返回多个Resource实例。

  

  ApplicationContext继承了ResourcePatternResolver,也就间接实现了ResourceLoader接口,这就是ApplicationContext支持Spring内统一资源加载策略的真相。

四、IOC容器功能实现

  综上,IOC容器会以某种方式加载Configuration Metadata(通常是XMl格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。

  基本上可以分为两个阶段,容器的启动阶段和Bean实例化阶段。

  1、容器启动阶段

  容器启动伊始,首先会通过某种途径加载Configuration Metadata,大部分情况下需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration Metadata进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器的启动工作就完成了。

  2、Bean实例化阶段

  经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到BeanDefinitionRegistry中,当某个请求方通过容器的getBean方法明确的请求某个对象,或者因依赖关系容器需要隐式的调用getBean方法时,就会触发第二个阶段的活动。

  该阶段,容器会首先检查所请求的对象之前是否已经初始化,如果没有,则会根据注册的BeanDefinition所提供的信息实例化请求对象,并为其注入依赖,如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完成之后,容器就会立即将其返回请求方使用。

Spring系列之IOC容器的更多相关文章

  1. Spring系列之IOC的原理及手动实现

    目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 导语 Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架.也是几乎所有J ...

  2. Spring.NET的IoC容器(The IoC container)——简介(Introduction)

    简介 这个章节介绍了Spring Framework的控制反转(Inversion of Control ,IoC)的实现原理. Spring.Core 程序集是Spring.NET的 IoC 容器实 ...

  3. Spring5源码解析系列一——IoC容器核心类图

    基本概念梳理 IoC(Inversion of Control,控制反转)就是把原来代码里需要实现的对象创建.依赖,反转给容器来帮忙实现.我们需要创建一个容器,同时需要一种描述来让容器知道要创建的对象 ...

  4. 比Spring简单的IoC容器

    比Spring简单的IoC容器 Spring 虽然比起EJB轻量了许多,但是因为它需要兼容许多不同的类库,导致现在Spring还是相当的庞大的,动不动就上40MB的jar包, 而且想要理解Spring ...

  5. Spring 系列教程之容器的功能

    Spring 系列教程之容器的功能 经过前面几章的分析,相信大家已经对 Spring 中的容器功能有了简单的了解,在前面的章节中我们一直以 BeanFacotry 接口以及它的默认实现类 XmlBea ...

  6. 使用Spring.NET的IoC容器

    使用Spring.NET的IoC容器 0. 辅助类库 using System; using System.Collections.Generic; using System.Linq; using ...

  7. Spring之一:IoC容器体系结构

    温故而知心. Spring IoC概述 常说spring的控制反转(依赖反转),看看维基百科的解释: 如果合作对象的引用或依赖关系的管理要由具体对象来完成,会导致代码的高度耦合和可测试性降低,这对复杂 ...

  8. Spring Framework------>version4.3.5.RELAESE----->Reference Documentation学习心得----->使用spring framework的IoC容器功能----->方法一:使用XML文件定义beans之间的依赖注入关系

    XML-based configuration metadata(使用XML文件定义beans之间的依赖注入关系) 第一部分 编程思路概述 step1,在XML文件中定义各个bean之间的依赖关系. ...

  9. Spring框架学习[IoC容器高级特性]

    1.通过前面4篇文章对Spring IoC容器的源码分析,我们已经基本上了解了Spring IoC容器对Bean定义资源的定位.读入和解析过程,同时也清楚了当用户通过getBean方法向IoC容器获取 ...

随机推荐

  1. 近期小结 之 Servlet规范及HTTP

    最近认真看了下Servlet 3.1的规范,略有收获,如下: 如果客户端不指定编码,Servlet容器必须使用ISO-8859-1编码来处理,且不能添加相应编码信息. Servlet 3 可以手动开启 ...

  2. 用Python中的tkinter模块作图(续)

    八.显示文字 用create_text在画布上写字.这个函数只需要两个坐标(文字x和y的位置),还有一个具名参数来接受要显示的文字.例如: >>> from tkinter impo ...

  3. java web 过滤器跟拦截器的区别和使用

    注:文章整理自知乎大牛以及百度网友(电脑网络分类达人 吕明),特此感谢! 一.过滤器 1.什么是过滤器? 过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上.过滤器可附加到一个 ...

  4. Error configuring application listener of class org.springframework.web.context.ContextLoaderListener

    严重:   Error   configuring   application   listener   of   class   org.springframework.web.context.Co ...

  5. (实用)Linux下Eclipse安装配置PyDev

    记录备忘. PyDev是Eclipse下支持Python开发的IDE插件,本文介绍安装和配置PyDev插件的过程. 一.安装PyDev插件两种安装方法: 1.在eclipse的Help->Ins ...

  6. python调用ansible接口API执行命令

    python版本:Python 2.6.6 ansible版本:ansible 2.3.1.0      下载地址:https://releases.ansible.com/ansible/ 调用脚本 ...

  7. Android 下载zip压缩文件并解压

    网上有很多介绍下载文件或者解压zip文件的文章,但是两者结合的不多,在此记录一下下载zip文件并直接解压的方法. 其实也很简单,就是把下载文件和解压zip文件结合到一起.下面即代码: URLConne ...

  8. VS2013启动浏览器链接(BrowserLink),导致页面脚本错误和页面加载变慢

    页面脚本出错场景:

  9. __setup、early_param的解析

    内核初始化时根据字符串匹配获得相应的处理函数,查找的时候有些麻烦. 写个脚本对将内核中的__setup和early_param显式做了解析: __setup #! /bin/bash grep '\& ...

  10. spring aop的配置

    http://www.cnblogs.com/oumyye/p/4480196.html http://blog.csdn.net/hjm4702192/article/details/1727766 ...