zoukankan      html  css  js  c++  java
  • 通俗理解spring源码(二)—— 资源定位与加载

    通俗理解spring源码(二)—— 资源定位与加载

    最开始学习spring的时候,一般都是这样用:

            ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
            User user = (User)context.getBean("user");

    这里的ApplicationContext也是一个容器,只不过是引用了一个DefaultListableBeanFactory,暂时先不用管,真正的容器还是DefaultListableBeanFactory,我们还是以DefaultListableBeanFactory的初始化为例,可以这样写:

    public static void main(String[] args) {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    Resource resource = new ClassPathResource("spring.xml");
    reader.loadBeanDefinitions(resource);
    User user = (User)factory.getBean("user");
    }

    1、DefaultListableBeanFactory实例化

    • new DefaultListableBeanFactory()这步操作实例化了一个工厂对象,初始化了一个容器,最终所有的bean都会放到这个容器中。
    • 在它的构造器中,首先调用父类构造:
        public DefaultListableBeanFactory() {
            super();
        }
    • 进入抽象类AbstractAutowireCapableBeanFactory中,AbstractAutowireCapableBeanFactory是DefaultListableBeanFactory的抽象父类,不记得的可以看看我上一篇博文,有个大致印象即可。在该抽象类构造中:
        public AbstractAutowireCapableBeanFactory() {
            super();
            //添加忽略给定接口的自动装配功能
            ignoreDependencyInterface(BeanNameAware.class);
            ignoreDependencyInterface(BeanFactoryAware.class);
            ignoreDependencyInterface(BeanClassLoaderAware.class);
        }
    • 这里的super(),可以点进去看看,没有任何操作。
    • ignoreDependencyInterface(),表示添加忽略给定接口的自动装配功能,暂不做详细介绍,大家可以参考这篇文章

    2、Resource资源封装

    • spring将所有的资源都封装成一个Resource,包括文件系统资源(FileSystemResource)、classpath资源(ClassPathResource)、url资源(UrlResource)等。
    • Resource接口定义了获取当前资源属性的方法,如存在性(Exists)、可读性(isReadable)、是否处于打开状态(isOpen)等。
      • public interface InputStreamSource {
        InputStream getInputStream() throws IOException;
        }
        public interface Resource extends InputStreamSource {
        
            boolean exists();
            default boolean isReadable() {
                return exists();
            }
            default boolean isOpen() {
                return false;
            }
            default boolean isFile() {
                return false;
            }
            URL getURL() throws IOException;
            URI getURI() throws IOException;
            File getFile() throws IOException; 
            default ReadableByteChannel readableChannel() throws IOException {
                return Channels.newChannel(getInputStream());
            }
            long contentLength() throws IOException;
            long lastModified() throws IOException;
            Resource createRelative(String relativePath) throws IOException;
            @Nullable
            String getFilename();
            String getDescription(); 

    2、XmlBeanDefinitionReader实例化

    • xml配置文件的读取是spring中最重要的功能,因为spring的大部分功能都是以配置作为切入点的。
    • XmlBeanDefinitionReader负责读取和解析已封装好xml配置文件的Resource,即ClassPathResource。

    • 这里先大致了解一下spring的资源加载体系:

      • ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource,如根据不同的前缀“file:”“http:”“jar:”等判断不同的资源。
      • BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个方法。BeanDefinition就是对你所定义的bean的class、id、alias、property等属性的封装,此时还没有实例化bean。
      • EnvironmentCapable:定义获取Environment方法,Environment就是对当前所激活的profile的封装。
      • DocumentLoader:定义从资源文件加载到转换为Document的功能。Document是对xml文档的封装,从中可以获取各个节点的数据。
      • AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现。
      • BeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition功能。
      • BeanDefinitionParserDelegate:定义解析Element的各种方法。真正解析xml的就是这个类,典型的委派模式。

    3、loadBeanDefinitions方法

    loadBeanDefinitions是整个资源加载的切入点,下面是该方法执行时序图:

    在该方法中会调用XmlBeanDefinitionReader的重载方法loadBeanDefinitions(Resource resource)。

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
            return loadBeanDefinitions(new EncodedResource(resource));
        }

    EncodedResource是处理资源文件的编码的,其中主要逻辑体现在getReader()方法中,我们可以在EncodedResource构造器中设置编码,spring就会使用相应的编码作为输入流的编码。

    public Reader getReader() throws IOException {
            if (this.charset != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.charset);
            }
            else if (this.encoding != null) {
                return new InputStreamReader(this.resource.getInputStream(), this.encoding);
            }
            else {
                return new InputStreamReader(this.resource.getInputStream());
            }
        }

    处理完编码后,进入到loadBeanDefinitions(new EncodedResource(resource))中:

        public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
            Assert.notNull(encodedResource, "EncodedResource must not be null");
            if (logger.isTraceEnabled()) {
                logger.trace("Loading XML bean definitions from " + encodedResource);
            }
            //记录已经加载的资源
            Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
            if (currentResources == null) {
                currentResources = new HashSet<>(4);
                this.resourcesCurrentlyBeingLoaded.set(currentResources);
            }
            //如果该资源已被加载,抛出异常
            if (!currentResources.add(encodedResource)) {
                throw new BeanDefinitionStoreException(
                        "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
            }
            try {
                //从encodedResource中取出原来的resource,得到输入流inputStream
                InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                    //inputSource不属于spring,是解析xml的一个工具,全路径为org.xml.sax.InputSource
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }
                    //真正的核心部分
                    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                }
                finally {
                    inputStream.close();
                }
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "IOException parsing XML document from " + encodedResource.getResource(), ex);
            }
            finally {
                currentResources.remove(encodedResource);
                if (currentResources.isEmpty()) {
                    this.resourcesCurrentlyBeingLoaded.remove();
                }
            }
        }

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

        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
    
            try {
                //从资源文件转换为document对象
                Document doc = doLoadDocument(inputSource, resource);
                //解析document,并注册beanDefiniton到工厂中
                int count = registerBeanDefinitions(doc, resource);
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + count + " bean definitions from " + resource);
                }
                return count;
            }
            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对象
                Document doc = doLoadDocument(inputSource, resource);
                //解析document,并注册beanDefiniton到工厂中
                int count = registerBeanDefinitions(doc, resource);

    其中registerBeanDefinitions(doc, resource)又是核心,逻辑非常复杂,先来看doLoadDocument(inputSource, resource)方法:

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

    这里获取对xml文件的验证模式,加载xml文件,并得到对应的document,获取xml的验证模式将在下一篇博客中讲解。

     

    走的太远,不要忘记为什么出发!现在再来看看这段代码:

       DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
       BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
       Resource resource = new ClassPathResource("spring.xml");
       reader.loadBeanDefinitions(resource);

     是不是清晰一些?总结如下:

    • 实例化工厂,作为bean的容器;
    • 实例化BeanDefinitionReader,负责读取xml;
    • 将资源文件路径封装为对应的resource对象;
    • 调用reader.loadBeanDefinitions(resource),实际上会先将resouce转换为document,再将document转换为beanDefinition,这一步是整个资源加载的核心。

     参考:spring源码深度解析

  • 相关阅读:
    洛谷 P1024 一元三次方程求解
    洛谷 P1025 数的划分
    假期一测
    洛谷 P1032 字符变换
    洛谷 P1033 自由落体
    洛谷 P1063 能量项链
    洛谷 P1072 Hankson 的趣味题
    洛谷 P1040 加分二叉树
    1013: [JSOI2008]球形空间产生器sphere
    1013: [JSOI2008]球形空间产生器sphere
  • 原文地址:https://www.cnblogs.com/xiaohang123/p/12689314.html
Copyright © 2011-2022 走看看