Spring IOC源码解读

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

2020-02-09  本文已影响0人  BugPool

所有文章已迁移至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开头的函数,基本上算是熬出头了

上一篇下一篇

猜你喜欢

热点阅读