通俗理解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源码深度解析

posted @ 2020-04-14 20:47  倔强的403  阅读(514)  评论(0编辑  收藏  举报