Spring系列之IOC容器

一、概述

  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所提供的信息实例化请求对象,并为其注入依赖,如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完成之后,容器就会立即将其返回请求方使用。

时间: 2024-10-10 14:22:24

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

Spring技术内幕——Spring Framework的IOC容器实现(一)

一.SpringIOC容器概述 IOC容器和依赖反转的模式 在面向对象的系统中,对象封装了数据和对数据的处理,对象的依赖关系常常体现在对数据和方法的依赖上.这些依赖关系可以通过把对象的依赖注入交给框架IOC容器来完成.他可以再解耦代码的同时提高了代码的可测试性. 依赖控制反转的实现由很多种方式,在Spring中,IOC容器是实现这个模式的载体,他可以再对象生成或者初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方法调用的依赖.这种依赖注入是可以递归的,对象被逐

使用Spring.NET的IoC容器

使用Spring.NET的IoC容器 0. 辅助类库 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SpringDemo.Pub { public static class Pub { public static string ServiceNameA = "SpringDemo.Lib.Oracle.OracleDatabase"; pub

比Spring简单的IoC容器

比Spring简单的IoC容器 Spring 虽然比起EJB轻量了许多,但是因为它需要兼容许多不同的类库,导致现在Spring还是相当的庞大的,动不动就上40MB的jar包, 而且想要理解Spring的内部运行机制,阅读它的代码非常重要, 但是往往它的代码非常的"多". 现在根据Spring对Bean的生命周期的处理, 编写出一款非常小的IoC容器, 没有了对XML的解析,而是通过对Config对象的构造而完成IoC配置文件的声明, 相比较XML的方式, 对重构软件非常具有好处, 并且

Spring 总览及 IOC 容器的使用 —— Spring 官方文档解读(一)

Spring 总览及 IOC 容器的使用 -- Spring 官方文档解读(一) 什么是 Spring? spring 这个词在不同情况下有不同意义.可以指 Spring 框架本身,但更多地被用来表示 Spring 整个家族的产品. 设计理念 学习框架必须要知道它的设计理念,Spring 框架有着以下的理念: Spring 让你在架构种的各个层面有更多的选择,并且允许你尽晚的做出决策.比如,你在项目完成后可以通过更改配置来切换持久层的提供者. Spring 具有强大的灵活性,它不在意你是如何完成

看完这篇你还敢说,不懂Spring中的IoC容器?

一. 什么是IoC 什么是耦合和内聚 耦合指的就是模块之间的依赖关系.模块间的依赖越多,则表示耦合度越高,相应的维护成本就越高.内聚指的是模块内功能之间的联系.模块内功能的联系越紧密,则表示内聚度越高,模块的职责也就越单一.所以在程序开发中应该尽量的降低耦合,提高内聚.也就是设计原则中的开闭原则和单一职责原则. 工厂模式 工厂模式就是用来解决程序间耦合的一种设计模式.可以把所有要创建的对象放在工厂的一个集合里,当需要使用这个对象的时候,直接从工厂里面取出来用就行. 工厂模式的优点: 一个调用者想

Spring学习之IOC容器1

在Spring IOC容器的设计中,有两个主要的容器系列,一个是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器的最基本功能:另一个是ApplicationContext应用上下文,它作为容器的高级形态而存在.应用上下文在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境做了许多适配. 用户在使用容器时,可以使用转义符“&”来得到FactoryBean本身,用来区分通过容器来获得FactoryBean产生的对象和获取FactoryBean本身.例如,如果MyBean是

Spring技术内幕——Spring Framework的IOC容器实现(二)

三.IOC容器的初始化过程 IOC容器的初始化时由前面介绍的refresh方法来启动的,这个方法标志着IOC容器的正式启动.这个启动包括BeanDefinition的Resource定位.载入和注册.下面我们将详细分析这三个实现过程,Spring把这三个过程分开,并使用不同的模块来完成,通过这样的设计让用户更加灵活的这三个过程进行剪裁和扩展,定义出最适合自己的IOC容器的初始化过程. 第一个过程: Resource定位过程,是指BeanDefinition的资源定位,他由ResourceLoad

好记性不如烂笔头83-spring3学习(4)-spring的BeanFactory(IoC容器)

我们一般把BeanFactory叫做IoC容器,叫ApplicationContext 为应用上下文(或者Spring容器) BeanFactory是spring框架的核心,实现依赖注入[使个组件的依赖关系从代码中独立出来,使用配置文件即可实现这种依赖关系]和bean声明周期的管理 . BeanFactory[IoC容器]启动过程:分为两个阶段,一个是容器启动阶段,另外一个是Bean实例化阶段 容器启动阶段:加载配置 -–> 分析配置信息 -–>装备到BeanDefinition -–>

Spring核心技术之IOC容器(一):IOC容器与Bean简介

最近开始研究Spring框架,今天学习Spring的核心内容IOC 与 Bean 1. Spring IOC 与 Bean 简介  Inversion of Control (IoC)即控制反转,也叫dependency injection (DI)依赖注入,Spring实现了一个基于配置文件的复杂工厂模式来提供实现控制反转. org.springframework.beans 和org.springframework.context包是Spring中实现IOC的基础包,其中BeanFactor