博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring系列之IOC容器
阅读量:6226 次
发布时间:2019-06-21

本文共 11359 字,大约阅读时间需要 37 分钟。

一、概述

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

转载地址:http://otyna.baihongyu.com/

你可能感兴趣的文章
sql性能优化总结
查看>>
windows 7 使用注册表创建影子账户和隐藏账户
查看>>
一个有用的python装饰器 -- 为执行程序加锁
查看>>
linux shell
查看>>
xfs文件系统优化
查看>>
eclipse.ini参数的含义和设置
查看>>
VirtualBox中常用的网络设置
查看>>
用 GetEnvironmentVariable 获取常用系统环境变量
查看>>
手把手安装ZABBIX2.2(CentOS6.5+Zabbix2.2.2)
查看>>
推送通知(本地推送+远程推送)详解
查看>>
ifconfig
查看>>
电子商务风险防控
查看>>
Android列表展示和手指滑动分页
查看>>
我的友情链接
查看>>
final 关键字修饰类、属性、方法的使用
查看>>
字符数组"student a am i"--》"i am a student"
查看>>
更改zabbix数据库mandatory
查看>>
使用Cocos Studio UI编辑器并在cocos2dx中加载
查看>>
对MYSQL进行压力测试
查看>>
运维自动化之 Cobbler 系统安装使用详解
查看>>