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

  • 相关阅读:
    我太难了
    树状数组模板
    题解 洛谷P1196 【[NOI2002]银河英雄传说】
    poj 2352 & Ural 1028 数星星 题解
    棋盘覆盖 题解
    2015 JSOI冬令营训练 彩色格子 题解
    题解 UVA12716 GCD等于XOR GCD XOR
    第一篇博客
    2019.8.26 小结
    2019.8.24 小结 (关于树状数组,线段树小结)
  • 原文地址:https://www.cnblogs.com/xujian2014/p/5050455.html
Copyright © 2011-2022 走看看