美文网首页Spring IOC源码解读
spring源码日记03: 资源文件读取

spring源码日记03: 资源文件读取

作者: BugPool | 来源:发表于2020-02-09 22:33 被阅读0次

    所有文章已迁移至csdn,csdn个人主页https://blog.csdn.net/chaitoudaren
    接下去将顺着流程图中6大转化过程,跟踪代码,逐一讲解

    spring解析阶段.jpg

    Xml文件 -> Resource

    xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
    
    1. 创建ClassPathResource对象
    // ClassPathResource.java
        public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
            // 路径不允许为空
            Assert.notNull(path, "Path must not be null");
            // 规范路径
            String pathToUse = StringUtils.cleanPath(path);
            // 如果路径以/开头,去掉/
            if (pathToUse.startsWith("/")) {
                pathToUse = pathToUse.substring(1);
            }
            this.path = pathToUse;
            // 设置classLoader
            this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
        }
    
    1. 从ClassPathResource获取InputStream
    // ClassPathResource.java
        public InputStream getInputStream() throws IOException {
            InputStream is;
            if (this.clazz != null) {
                // 使用类对象的getResourceAsStream
                is = this.clazz.getResourceAsStream(this.path);
            }
            else if (this.classLoader != null) {
                // 使用类加载器的getResourceAsStream
                is = this.classLoader.getResourceAsStream(this.path);
            }
            else {
                // 使用ClassLoader类的getSystemResourceAsStream
                is = ClassLoader.getSystemResourceAsStream(this.path);
            }
            if (is == null) {
                // 文件不存在
                throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
            }
            return is;
        }
    

    类加载器涉及面太宽,不是本专题的重点,有兴趣的可以参考下:
    老大难的 Java ClassLoader

    Resource -> InputStram

    获取到Resource以后,继续跟踪代码

    1. 实例化XmlBeanFactory
    // XmlBeanFactory.java
    public XmlBeanFactory(Resource resource) throws BeansException {
            this(resource, null);
        }
    
    // XmlBeanFactory.java
        public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
            super(parentBeanFactory);
            // 核心逻辑,也是我们关注的重点
            this.reader.loadBeanDefinitions(resource);
        }
    
    1. 忽略指定接口的自动装配功能
      在初始化XmlBeanFactory中,需要先初始化父类,而父类中有这么一段代码需要注意。spring各类感知器也就是Aware的使用可以参考:spring BeanPostProcessor 生命周期
    // AbstractAutowireCapableBeanFactory
        public AbstractAutowireCapableBeanFactory() {
            super();
            /**
             * 自动装配时忽略给定的依赖接口
             * 正常情况下,如果A类中有自动装配的属性B,则B如果未被创建则会被自动创建并自动装配到A当中
             * 但是如果A实现了BeanFactoryAware,B是BeanFactory,则B不会被自动装配,而是通过调用重写的setBeanFactory方法进行注入
             */
            ignoreDependencyInterface(BeanNameAware.class);
            ignoreDependencyInterface(BeanFactoryAware.class);
            ignoreDependencyInterface(BeanClassLoaderAware.class);
        }
    
    
    1. 初始化XmlBeanDefinitionReader对象
      XmlBeanFactory读取Xml的工作并没有自己完成,而是委托给了自己的属性reader,因此,在初始化XmlBeanFactory时,我们需要初始化XmlBeanDefinitionReader
    // XmlBeanFactory.java
    // 委托读取xml对象
    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    

    初始化XmlBeanDefinitionReader最主要的工作是设置当前的资源加载器以及当前的相关环境变量,不做深究,将重点放在核心逻辑上

    // XmlBeanDefinitionReader.java
        protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
            Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
            this.registry = registry;
    
            // Determine ResourceLoader to use.
            if (this.registry instanceof ResourceLoader) {
                this.resourceLoader = (ResourceLoader) this.registry;
            }
            else {
                this.resourceLoader = new PathMatchingResourcePatternResolver();
            }
    
            // Inherit Environment if possible
            if (this.registry instanceof EnvironmentCapable) {
                // 有可继承的环境,则直接使用
                this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
            }
            else {
                // 否则新建一个环境,包括系统环境属性,JVM系统环境属性等
                this.environment = new StandardEnvironment();
            }
        }
    
    1. 对资源文件进行编码
    // XmlBeanDefinitionReader.java
        public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
            // 对资源进行编码
            return loadBeanDefinitions(new EncodedResource(resource));
        }
    
    1. 获取InputStream
      获取InputStream无非就是调用resource的getInputStream方法,再进行编码操作。
      值得注意的是currentResources这个对象,该对象用于处理资源相互循环引用的检测。在spring中大量使用了这种思想,包括最著名的循环依赖也是使用这种方法进行检测的,需要多加理解
    // XmlBeanDefinitionReader.java
    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);
            }
    
            /**
             * currentResources保存着当前正在加载的资源,用于处理资源循环引用的问题
             * 例如:A资源引入B,B又引入A资源。则加载A时会将B资源引入进来,于是currentResources中包含AB,
             *      而B资源又要将A资源引入,此时currentResources已经包含A,顾添加失败,抛出异常
             */
            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 {
                // 获取输入流
                InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                    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();
                }
            }
        }
    

    至此,Xml文件已经转化为InputStream,虽然关键代码只有几行,但是spring做了大量的工作,下一节将继续跟踪核心逻辑doLoadBeanDefinitions。在spring中有一个有趣规则,就是任何以do开头的函数才是真正的核心逻辑,因此以后我们看到do开头的函数,基本上算是熬出头了

    相关文章

      网友评论

        本文标题:spring源码日记03: 资源文件读取

        本文链接:https://www.haomeiwen.com/subject/kcnnxhtx.html